/**
 * interact.js v1.2.6
 *
 * Copyright (c) 2012-2015 Taye Adeyemi <dev@taye.me>
 * Open source under the MIT License.
 * https://raw.github.com/taye/interact.js/master/LICENSE
 *
 * This file has been monkey patched to pass the originalEvent on to drag events
 */
(function(realWindow){
  'use strict';

    // return early if there's no window to work with (eg. Node.js)
  if (!realWindow){ return; }

  var // get wrapped window if using Shadow DOM polyfill
    window = (function(){
            // create a TextNode
      var el = realWindow.document.createTextNode('');

            // check if it's wrapped by a polyfill
      if (el.ownerDocument !== realWindow.document
                && typeof realWindow.wrap === 'function'
                && realWindow.wrap(el) === el){
                // return wrapped window
        return realWindow.wrap(realWindow);
      }

            // no Shadow DOM polyfil or native implementation
      return realWindow;
    }()),

    document = window.document,
    DocumentFragment = window.DocumentFragment || blank,
    SVGElement = window.SVGElement || blank,
    SVGSVGElement = window.SVGSVGElement || blank,
    SVGElementInstance = window.SVGElementInstance || blank,
    HTMLElement = window.HTMLElement || window.Element,

    PointerEvent = (window.PointerEvent || window.MSPointerEvent),
    pEventTypes,

    hypot = Math.hypot || function(x, y){ return Math.sqrt(x * x + y * y); },

    tmpXY = {},     // reduce object creation in getXY()

    documents = [],   // all documents being listened to

    interactables = [],   // all set interactables
    interactions = [],   // all interactions

    dynamicDrop = false,

        // {
        //      type: {
        //          selectors: ['selector', ...],
        //          contexts : [document, ...],
        //          listeners: [[listener, useCapture], ...]
        //      }
        //  }
    delegatedEvents = {},

    defaultOptions = {
      base: {
        accept: null,
        actionChecker: null,
        styleCursor: true,
        preventDefault: 'auto',
        origin: { x: 0, y: 0 },
        deltaSource: 'page',
        allowFrom: null,
        ignoreFrom: null,
        _context: document,
        dropChecker: null
      },

      drag: {
        enabled: false,
        manualStart: true,
        max: Infinity,
        maxPerElement: 1,

        snap: null,
        restrict: null,
        inertia: null,
        autoScroll: null,

        axis: 'xy'
      },

      drop: {
        enabled: false,
        accept: null,
        overlap: 'pointer'
      },

      resize: {
        enabled: false,
        manualStart: false,
        max: Infinity,
        maxPerElement: 1,

        snap: null,
        restrict: null,
        inertia: null,
        autoScroll: null,

        square: false,
        preserveAspectRatio: false,
        axis: 'xy',

                // use default margin
        margin: NaN,

                // object with props left, right, top, bottom which are
                // true/false values to resize when the pointer is over that edge,
                // CSS selectors to match the handles for each direction
                // or the Elements for each handle
        edges: null,

                // a value of 'none' will limit the resize rect to a minimum of 0x0
                // 'negate' will alow the rect to have negative width/height
                // 'reposition' will keep the width/height positive by swapping
                // the top and bottom edges and/or swapping the left and right edges
        invert: 'none'
      },

      gesture: {
        manualStart: false,
        enabled: false,
        max: Infinity,
        maxPerElement: 1,

        restrict: null
      },

      perAction: {
        manualStart: false,
        max: Infinity,
        maxPerElement: 1,

        snap: {
          enabled: false,
          endOnly: false,
          range: Infinity,
          targets: null,
          offsets: null,

          relativePoints: null
        },

        restrict: {
          enabled: false,
          endOnly: false
        },

        autoScroll: {
          enabled: false,
          container: null,     // the item that is scrolled (Window or HTMLElement)
          margin: 60,
          speed: 300       // the scroll speed in pixels per second
        },

        inertia: {
          enabled: false,
          resistance: 10,    // the lambda in exponential decay
          minSpeed: 100,   // target speed must be above this for inertia to start
          endSpeed: 10,    // the speed at which inertia is slow enough to stop
          allowResume: true,  // allow resuming an action in inertia phase
          zeroResumeDelta: true,  // if an action is resumed after launch, set dx/dy to 0
          smoothEndDuration: 300    // animate to snap/restrict endOnly if there's no inertia
        }
      },

      _holdDuration: 600
    },

        // Things related to autoScroll
    autoScroll = {
      interaction: null,
      i: null,    // the handle returned by window.setInterval
      x: 0, y: 0, // Direction each pulse is to scroll in

            // scroll the window by the values in scroll.x/y
      scroll: function(){
        var options = autoScroll.interaction.target.options[autoScroll.interaction.prepared.name].autoScroll,
          container = options.container || getWindow(autoScroll.interaction.element),
          now = new Date().getTime(),
                    // change in time in seconds
          dtx = (now - autoScroll.prevTimeX) / 1000,
          dty = (now - autoScroll.prevTimeY) / 1000,
          vx, vy, sx, sy;

                // displacement
        if (options.velocity){
          vx = options.velocity.x;
          vy = options.velocity.y;
        }
        else {
          vx = vy = options.speed;
        }

        sx = vx * dtx;
        sy = vy * dty;

        if (sx >= 1 || sy >= 1){
          if (isWindow(container)){
            container.scrollBy(autoScroll.x * sx, autoScroll.y * sy);
          }
          else if (container){
            container.scrollLeft += autoScroll.x * sx;
            container.scrollTop += autoScroll.y * sy;
          }

          if (sx >= 1) autoScroll.prevTimeX = now;
          if (sy >= 1) autoScroll.prevTimeY = now;
        }

        if (autoScroll.isScrolling){
          cancelFrame(autoScroll.i);
          autoScroll.i = reqFrame(autoScroll.scroll);
        }
      },

      isScrolling: false,
      prevTimeX: 0,
      prevTimeY: 0,

      start: function(interaction){
        autoScroll.isScrolling = true;
        cancelFrame(autoScroll.i);

        autoScroll.interaction = interaction;
        autoScroll.prevTimeX = new Date().getTime();
        autoScroll.prevTimeY = new Date().getTime();
        autoScroll.i = reqFrame(autoScroll.scroll);
      },

      stop: function(){
        autoScroll.isScrolling = false;
        cancelFrame(autoScroll.i);
      }
    },

        // Does the browser support touch input?
    supportsTouch = (('ontouchstart' in window) || window.DocumentTouch && document instanceof window.DocumentTouch),

        // Does the browser support PointerEvents
    supportsPointerEvent = PointerEvent && !/Chrome/.test(navigator.userAgent),

        // Less Precision with touch input
    margin = supportsTouch || supportsPointerEvent ? 20 : 10,

    pointerMoveTolerance = 1,

        // for ignoring browser's simulated mouse events
    prevTouchTime = 0,

        // Allow this many interactions to happen simultaneously
    maxInteractions = Infinity,

        // Check if is IE9 or older
    actionCursors = (document.all && !window.atob) ? {
      drag: 'move',
      resizex: 'e-resize',
      resizey: 's-resize',
      resizexy: 'se-resize',

      resizetop: 'n-resize',
      resizeleft: 'w-resize',
      resizebottom: 's-resize',
      resizeright: 'e-resize',
      resizetopleft: 'se-resize',
      resizebottomright: 'se-resize',
      resizetopright: 'ne-resize',
      resizebottomleft: 'ne-resize',

      gesture: ''
    } : {
      drag: 'move',
      resizex: 'ew-resize',
      resizey: 'ns-resize',
      resizexy: 'nwse-resize',

      resizetop: 'ns-resize',
      resizeleft: 'ew-resize',
      resizebottom: 'ns-resize',
      resizeright: 'ew-resize',
      resizetopleft: 'nwse-resize',
      resizebottomright: 'nwse-resize',
      resizetopright: 'nesw-resize',
      resizebottomleft: 'nesw-resize',

      gesture: ''
    },

    actionIsEnabled = {
      drag: true,
      resize: true,
      gesture: true
    },

        // because Webkit and Opera still use 'mousewheel' event type
    wheelEvent = 'onmousewheel' in document ? 'mousewheel' : 'wheel',

    eventTypes = [
      'dragstart',
      'dragmove',
      'draginertiastart',
      'dragend',
      'dragenter',
      'dragleave',
      'dropactivate',
      'dropdeactivate',
      'dropmove',
      'drop',
      'resizestart',
      'resizemove',
      'resizeinertiastart',
      'resizeend',
      'gesturestart',
      'gesturemove',
      'gestureinertiastart',
      'gestureend',

      'down',
      'move',
      'up',
      'cancel',
      'tap',
      'doubletap',
      'hold'
    ],

    globalEvents = {},

        // Opera Mobile must be handled differently
    isOperaMobile = navigator.appName == 'Opera' &&
            supportsTouch &&
            navigator.userAgent.match('Presto'),

        // scrolling doesn't change the result of getClientRects on iOS 7
    isIOS7 = (/iP(hone|od|ad)/.test(navigator.platform)
                         && /OS 7[^\d]/.test(navigator.appVersion)),

        // prefix matchesSelector
    prefixedMatchesSelector = 'matches' in Element.prototype ?
                'matches' : 'webkitMatchesSelector' in Element.prototype ?
                    'webkitMatchesSelector' : 'mozMatchesSelector' in Element.prototype ?
                        'mozMatchesSelector' : 'oMatchesSelector' in Element.prototype ?
                            'oMatchesSelector' : 'msMatchesSelector',

        // will be polyfill function if browser is IE8
    ie8MatchesSelector,

        // native requestAnimationFrame or polyfill
    reqFrame = realWindow.requestAnimationFrame,
    cancelFrame = realWindow.cancelAnimationFrame,

        // Events wrapper
    events = (function(){
      var useAttachEvent = ('attachEvent' in window) && !('addEventListener' in window),
        addEvent = useAttachEvent ? 'attachEvent' : 'addEventListener',
        removeEvent = useAttachEvent ? 'detachEvent' : 'removeEventListener',
        on = useAttachEvent ? 'on' : '',

        elements = [],
        targets = [],
        attachedListeners = [];

      function add(element, type, listener, useCapture){
        var elementIndex = indexOf(elements, element),
          target = targets[elementIndex];

        if (!target){
          target = {
            events: {},
            typeCount: 0
          };

          elementIndex = elements.push(element) - 1;
          targets.push(target);

          attachedListeners.push((useAttachEvent ? {
            supplied: [],
            wrapped: [],
            useCount: []
          } : null));
        }

        if (!target.events[type]){
          target.events[type] = [];
          target.typeCount++;
        }

        if (!contains(target.events[type], listener)){
          var ret;

          if (useAttachEvent){
            var listeners = attachedListeners[elementIndex],
              listenerIndex = indexOf(listeners.supplied, listener);

            var wrapped = listeners.wrapped[listenerIndex] || function(event){
              if (!event.immediatePropagationStopped){
                event.target = event.srcElement;
                event.currentTarget = element;

                event.preventDefault = event.preventDefault || preventDef;
                event.stopPropagation = event.stopPropagation || stopProp;
                event.stopImmediatePropagation = event.stopImmediatePropagation || stopImmProp;

                if (/mouse|click/.test(event.type)){
                  event.pageX = event.clientX + getWindow(element).document.documentElement.scrollLeft;
                  event.pageY = event.clientY + getWindow(element).document.documentElement.scrollTop;
                }

                listener(event);
              }
            };

            ret = element[addEvent](on + type, wrapped, Boolean(useCapture));

            if (listenerIndex === -1){
              listeners.supplied.push(listener);
              listeners.wrapped.push(wrapped);
              listeners.useCount.push(1);
            }
            else {
              listeners.useCount[listenerIndex]++;
            }
          }
          else {
            ret = element[addEvent](type, listener, useCapture || false);
          }
          target.events[type].push(listener);

          return ret;
        }
      }

      function remove(element, type, listener, useCapture){
        var i,
          elementIndex = indexOf(elements, element),
          target = targets[elementIndex],
          listeners,
          listenerIndex,
          wrapped = listener;

        if (!target || !target.events){
          return;
        }

        if (useAttachEvent){
          listeners = attachedListeners[elementIndex];
          listenerIndex = indexOf(listeners.supplied, listener);
          wrapped = listeners.wrapped[listenerIndex];
        }

        if (type === 'all'){
          for (type in target.events){
            if (target.events.hasOwnProperty(type)){
              remove(element, type, 'all');
            }
          }
          return;
        }

        if (target.events[type]){
          var len = target.events[type].length;

          if (listener === 'all'){
            for (i = 0; i < len; i++){
              remove(element, type, target.events[type][i], Boolean(useCapture));
            }
            return;
          } else {
            for (i = 0; i < len; i++){
              if (target.events[type][i] === listener){
                element[removeEvent](on + type, wrapped, useCapture || false);
                target.events[type].splice(i, 1);

                if (useAttachEvent && listeners){
                  listeners.useCount[listenerIndex]--;
                  if (listeners.useCount[listenerIndex] === 0){
                    listeners.supplied.splice(listenerIndex, 1);
                    listeners.wrapped.splice(listenerIndex, 1);
                    listeners.useCount.splice(listenerIndex, 1);
                  }
                }

                break;
              }
            }
          }

          if (target.events[type] && target.events[type].length === 0){
            target.events[type] = null;
            target.typeCount--;
          }
        }

        if (!target.typeCount){
          targets.splice(elementIndex, 1);
          elements.splice(elementIndex, 1);
          attachedListeners.splice(elementIndex, 1);
        }
      }

      function preventDef(){
        this.returnValue = false;
      }

      function stopProp(){
        this.cancelBubble = true;
      }

      function stopImmProp(){
        this.cancelBubble = true;
        this.immediatePropagationStopped = true;
      }

      return {
        add: add,
        remove: remove,
        useAttachEvent: useAttachEvent,

        _elements: elements,
        _targets: targets,
        _attachedListeners: attachedListeners
      };
    }());

  function blank(){}

  function isElement(o){
    if (!o || (typeof o !== 'object')){ return false; }

    var _window = getWindow(o) || window;

    return (/object|function/.test(typeof _window.Element)
            ? o instanceof _window.Element // DOM2
            : o.nodeType === 1 && typeof o.nodeName === 'string');
  }
  function isWindow(thing){ return thing === window || !!(thing && thing.Window) && (thing instanceof thing.Window); }
  function isDocFrag(thing){ return !!thing && thing instanceof DocumentFragment; }
  function isArray(thing){
    return isObject(thing)
                && (typeof thing.length !== undefined)
                && isFunction(thing.splice);
  }
  function isObject(thing){ return !!thing && (typeof thing === 'object'); }
  function isFunction(thing){ return typeof thing === 'function'; }
  function isNumber(thing){ return typeof thing === 'number'; }
  function isBool(thing){ return typeof thing === 'boolean'; }
  function isString(thing){ return typeof thing === 'string'; }

  function trySelector(value){
    if (!isString(value)){ return false; }

        // an exception will be raised if it is invalid
    document.querySelector(value);
    return true;
  }

  function extend(dest, source){
    for (var prop in source){
      dest[prop] = source[prop];
    }
    return dest;
  }

  var prefixedPropREs = {
    webkit: /(Movement[XY]|Radius[XY]|RotationAngle|Force)$/
  };

  function pointerExtend(dest, source){
    for (var prop in source){
      var deprecated = false;

          // skip deprecated prefixed properties
      for (var vendor in prefixedPropREs){
        if (prop.indexOf(vendor) === 0 && prefixedPropREs[vendor].test(prop)){
          deprecated = true;
          break;
        }
      }

      if (!deprecated){
        dest[prop] = source[prop];
      }
    }
    return dest;
  }

  function copyCoords(dest, src){
    dest.page = dest.page || {};
    dest.page.x = src.page.x;
    dest.page.y = src.page.y;

    dest.client = dest.client || {};
    dest.client.x = src.client.x;
    dest.client.y = src.client.y;

    dest.timeStamp = src.timeStamp;
  }

  function setEventXY(targetObj, pointers, interaction){
    var pointer = (pointers.length > 1
                       ? pointerAverage(pointers)
                       : pointers[0]);

    getPageXY(pointer, tmpXY, interaction);
    targetObj.page.x = tmpXY.x;
    targetObj.page.y = tmpXY.y;

    getClientXY(pointer, tmpXY, interaction);
    targetObj.client.x = tmpXY.x;
    targetObj.client.y = tmpXY.y;

    targetObj.timeStamp = new Date().getTime();
  }

  function setEventDeltas(targetObj, prev, cur){
    targetObj.page.x = cur.page.x - prev.page.x;
    targetObj.page.y = cur.page.y - prev.page.y;
    targetObj.client.x = cur.client.x - prev.client.x;
    targetObj.client.y = cur.client.y - prev.client.y;
    targetObj.timeStamp = new Date().getTime() - prev.timeStamp;

        // set pointer velocity
    var dt = Math.max(targetObj.timeStamp / 1000, 0.001);
    targetObj.page.speed = hypot(targetObj.page.x, targetObj.page.y) / dt;
    targetObj.page.vx = targetObj.page.x / dt;
    targetObj.page.vy = targetObj.page.y / dt;

    targetObj.client.speed = hypot(targetObj.client.x, targetObj.page.y) / dt;
    targetObj.client.vx = targetObj.client.x / dt;
    targetObj.client.vy = targetObj.client.y / dt;
  }

  function isNativePointer(pointer){
    return (pointer instanceof window.Event
            || (supportsTouch && window.Touch && pointer instanceof window.Touch));
  }

    // Get specified X/Y coords for mouse or event.touches[0]
  function getXY(type, pointer, xy){
    xy = xy || {};
    type = type || 'page';

    xy.x = pointer[type + 'X'];
    xy.y = pointer[type + 'Y'];

    return xy;
  }

  function getPageXY(pointer, page){
    page = page || {};

        // Opera Mobile handles the viewport and scrolling oddly
    if (isOperaMobile && isNativePointer(pointer)){
      getXY('screen', pointer, page);

      page.x += window.scrollX;
      page.y += window.scrollY;
    }
    else {
      getXY('page', pointer, page);
    }

    return page;
  }

  function getClientXY(pointer, client){
    client = client || {};

    if (isOperaMobile && isNativePointer(pointer)){
            // Opera Mobile handles the viewport and scrolling oddly
      getXY('screen', pointer, client);
    }
    else {
      getXY('client', pointer, client);
    }

    return client;
  }

  function getScrollXY(win){
    win = win || window;
    return {
      x: win.scrollX || win.document.documentElement.scrollLeft,
      y: win.scrollY || win.document.documentElement.scrollTop
    };
  }

  function getPointerId(pointer){
    return isNumber(pointer.pointerId) ? pointer.pointerId : pointer.identifier;
  }

  function getActualElement(element){
    return (element instanceof SVGElementInstance
            ? element.correspondingUseElement
            : element);
  }

  function getWindow(node){
    if (isWindow(node)){
      return node;
    }

    var rootNode = (node.ownerDocument || node);

    return rootNode.defaultView || rootNode.parentWindow || window;
  }

  function getElementClientRect(element){
    var clientRect = (element instanceof SVGElement
                            ? element.getBoundingClientRect()
                            : element.getClientRects()[0]);

    return clientRect && {
      left: clientRect.left,
      right: clientRect.right,
      top: clientRect.top,
      bottom: clientRect.bottom,
      width: clientRect.width || clientRect.right - clientRect.left,
      height: clientRect.height || clientRect.bottom - clientRect.top
    };
  }

  function getElementRect(element){
    var clientRect = getElementClientRect(element);

    if (!isIOS7 && clientRect){
      var scroll = getScrollXY(getWindow(element));

      clientRect.left += scroll.x;
      clientRect.right += scroll.x;
      clientRect.top += scroll.y;
      clientRect.bottom += scroll.y;
    }

    return clientRect;
  }

  function getTouchPair(event){
    var touches = [];

        // array of touches is supplied
    if (isArray(event)){
      touches[0] = event[0];
      touches[1] = event[1];
    }
        // an event
    else {
      if (event.type === 'touchend'){
        if (event.touches.length === 1){
          touches[0] = event.touches[0];
          touches[1] = event.changedTouches[0];
        }
        else if (event.touches.length === 0){
          touches[0] = event.changedTouches[0];
          touches[1] = event.changedTouches[1];
        }
      }
      else {
        touches[0] = event.touches[0];
        touches[1] = event.touches[1];
      }
    }

    return touches;
  }

  function pointerAverage(pointers){
    var average = {
      pageX: 0,
      pageY: 0,
      clientX: 0,
      clientY: 0,
      screenX: 0,
      screenY: 0
    };
    var prop;

    for (var i = 0; i < pointers.length; i++){
      for (prop in average){
        average[prop] += pointers[i][prop];
      }
    }
    for (prop in average){
      average[prop] /= pointers.length;
    }

    return average;
  }

  function touchBBox(event){
    if (!event.length && !(event.touches && event.touches.length > 1)){
      return;
    }

    var touches = getTouchPair(event),
      minX = Math.min(touches[0].pageX, touches[1].pageX),
      minY = Math.min(touches[0].pageY, touches[1].pageY),
      maxX = Math.max(touches[0].pageX, touches[1].pageX),
      maxY = Math.max(touches[0].pageY, touches[1].pageY);

    return {
      x: minX,
      y: minY,
      left: minX,
      top: minY,
      width: maxX - minX,
      height: maxY - minY
    };
  }

  function touchDistance(event, deltaSource){
    deltaSource = deltaSource || defaultOptions.deltaSource;

    var sourceX = deltaSource + 'X',
      sourceY = deltaSource + 'Y',
      touches = getTouchPair(event);

    var dx = touches[0][sourceX] - touches[1][sourceX],
      dy = touches[0][sourceY] - touches[1][sourceY];

    return hypot(dx, dy);
  }

  function touchAngle(event, prevAngle, deltaSource){
    deltaSource = deltaSource || defaultOptions.deltaSource;

    var sourceX = deltaSource + 'X',
      sourceY = deltaSource + 'Y',
      touches = getTouchPair(event),
      dx = touches[0][sourceX] - touches[1][sourceX],
      dy = touches[0][sourceY] - touches[1][sourceY],
      angle = 180 * Math.atan(dy / dx) / Math.PI;

    if (isNumber(prevAngle)){
      var dr = angle - prevAngle,
        drClamped = dr % 360;

      if (drClamped > 315){
        angle -= 360 + (angle / 360) | 0 * 360;
      }
      else if (drClamped > 135){
        angle -= 180 + (angle / 360) | 0 * 360;
      }
            else if (drClamped < -315){
              angle += 360 + (angle / 360) | 0 * 360;
            }
            else if (drClamped < -135){
              angle += 180 + (angle / 360) | 0 * 360;
            }
    }

    return angle;
  }

  function getOriginXY(interactable, element){
    var origin = interactable
                ? interactable.options.origin
                : defaultOptions.origin;

    if (origin === 'parent'){
      origin = parentElement(element);
    }
    else if (origin === 'self'){
      origin = interactable.getRect(element);
    }
        else if (trySelector(origin)){
          origin = closest(element, origin) || { x: 0, y: 0 };
        }

    if (isFunction(origin)){
      origin = origin(interactable && element);
    }

    if (isElement(origin)){
      origin = getElementRect(origin);
    }

    origin.x = ('x' in origin) ? origin.x : origin.left;
    origin.y = ('y' in origin) ? origin.y : origin.top;

    return origin;
  }

    // http://stackoverflow.com/a/5634528/2280888
  function _getQBezierValue(t, p1, p2, p3){
    var iT = 1 - t;
    return iT * iT * p1 + 2 * iT * t * p2 + t * t * p3;
  }

  function getQuadraticCurvePoint(startX, startY, cpX, cpY, endX, endY, position){
    return {
      x: _getQBezierValue(position, startX, cpX, endX),
      y: _getQBezierValue(position, startY, cpY, endY)
    };
  }

    // http://gizma.com/easing/
  function easeOutQuad(t, b, c, d){
    t /= d;
    return -c * t * (t - 2) + b;
  }

  function nodeContains(parent, child){
    while (child){
      if (child === parent){
        return true;
      }

      child = child.parentNode;
    }

    return false;
  }

  function closest(child, selector){
    var parent = parentElement(child);

    while (isElement(parent)){
      if (matchesSelector(parent, selector)){ return parent; }

      parent = parentElement(parent);
    }

    return null;
  }

  function parentElement(node){
    var parent = node.parentNode;

    if (isDocFrag(parent)){
            // skip past #shado-root fragments
      while ((parent = parent.host) && isDocFrag(parent)){}

      return parent;
    }

    return parent;
  }

  function inContext(interactable, element){
    return interactable._context === element.ownerDocument
                || nodeContains(interactable._context, element);
  }

  function testIgnore(interactable, interactableElement, element){
    var ignoreFrom = interactable.options.ignoreFrom;

    if (!ignoreFrom || !isElement(element)){ return false; }

    if (isString(ignoreFrom)){
      return matchesUpTo(element, ignoreFrom, interactableElement);
    }
    else if (isElement(ignoreFrom)){
      return nodeContains(ignoreFrom, element);
    }

    return false;
  }

  function testAllow(interactable, interactableElement, element){
    var allowFrom = interactable.options.allowFrom;

    if (!allowFrom){ return true; }

    if (!isElement(element)){ return false; }

    if (isString(allowFrom)){
      return matchesUpTo(element, allowFrom, interactableElement);
    }
    else if (isElement(allowFrom)){
      return nodeContains(allowFrom, element);
    }

    return false;
  }

  function checkAxis(axis, interactable){
    if (!interactable){ return false; }

    var thisAxis = interactable.options.drag.axis;

    return (axis === 'xy' || thisAxis === 'xy' || thisAxis === axis);
  }

  function checkSnap(interactable, action){
    var options = interactable.options;

    if (/^resize/.test(action)){
      action = 'resize';
    }

    return options[action].snap && options[action].snap.enabled;
  }

  function checkRestrict(interactable, action){
    var options = interactable.options;

    if (/^resize/.test(action)){
      action = 'resize';
    }

    return options[action].restrict && options[action].restrict.enabled;
  }

  function checkAutoScroll(interactable, action){
    var options = interactable.options;

    if (/^resize/.test(action)){
      action = 'resize';
    }

    return options[action].autoScroll && options[action].autoScroll.enabled;
  }

  function withinInteractionLimit(interactable, element, action){
    var options = interactable.options,
      maxActions = options[action.name].max,
      maxPerElement = options[action.name].maxPerElement,
      activeInteractions = 0,
      targetCount = 0,
      targetElementCount = 0;

    for (var i = 0, len = interactions.length; i < len; i++){
      var interaction = interactions[i],
        otherAction = interaction.prepared.name,
        active = interaction.interacting();

      if (!active){ continue; }

      activeInteractions++;

      if (activeInteractions >= maxInteractions){
        return false;
      }

      if (interaction.target !== interactable){ continue; }

      targetCount += (otherAction === action.name) | 0;

      if (targetCount >= maxActions){
        return false;
      }

      if (interaction.element === element){
        targetElementCount++;

        if (otherAction !== action.name || targetElementCount >= maxPerElement){
          return false;
        }
      }
    }

    return maxInteractions > 0;
  }

    // Test for the element that's "above" all other qualifiers
  function indexOfDeepestElement(elements){
    var dropzone,
      deepestZone = elements[0],
      index = deepestZone ? 0 : -1,
      parent,
      deepestZoneParents = [],
      dropzoneParents = [],
      child,
      i,
      n;

    for (i = 1; i < elements.length; i++){
      dropzone = elements[i];

            // an element might belong to multiple selector dropzones
      if (!dropzone || dropzone === deepestZone){
        continue;
      }

      if (!deepestZone){
        deepestZone = dropzone;
        index = i;
        continue;
      }

            // check if the deepest or current are document.documentElement or document.rootElement
            // - if the current dropzone is, do nothing and continue
      if (dropzone.parentNode === dropzone.ownerDocument){
        continue;
      }
            // - if deepest is, update with the current dropzone and continue to next
      else if (deepestZone.parentNode === dropzone.ownerDocument){
        deepestZone = dropzone;
        index = i;
        continue;
      }

      if (!deepestZoneParents.length){
        parent = deepestZone;
        while (parent.parentNode && parent.parentNode !== parent.ownerDocument){
          deepestZoneParents.unshift(parent);
          parent = parent.parentNode;
        }
      }

            // if this element is an svg element and the current deepest is
            // an HTMLElement
      if (deepestZone instanceof HTMLElement
                && dropzone instanceof SVGElement
                && !(dropzone instanceof SVGSVGElement)){
        if (dropzone === deepestZone.parentNode){
          continue;
        }

        parent = dropzone.ownerSVGElement;
      }
      else {
        parent = dropzone;
      }

      dropzoneParents = [];

      while (parent.parentNode !== parent.ownerDocument){
        dropzoneParents.unshift(parent);
        parent = parent.parentNode;
      }

      n = 0;

            // get (position of last common ancestor) + 1
      while (dropzoneParents[n] && dropzoneParents[n] === deepestZoneParents[n]){
        n++;
      }

      var parents = [
        dropzoneParents[n - 1],
        dropzoneParents[n],
        deepestZoneParents[n]
      ];

      child = parents[0].lastChild;

      while (child){
        if (child === parents[1]){
          deepestZone = dropzone;
          index = i;
          deepestZoneParents = [];

          break;
        }
        else if (child === parents[2]){
          break;
        }

        child = child.previousSibling;
      }
    }

    return index;
  }

  function Interaction(){
    this.target = null; // current interactable being interacted with
    this.element = null; // the target element of the interactable
    this.dropTarget = null; // the dropzone a drag target might be dropped into
    this.dropElement = null; // the element at the time of checking
    this.prevDropTarget = null; // the dropzone that was recently dragged away from
    this.prevDropElement = null; // the element at the time of checking

    this.prepared = {     // action that's ready to be fired on next move event
      name: null,
      axis: null,
      edges: null
    };

    this.matches = [];   // all selectors that are matched by target element
    this.matchElements = [];   // corresponding elements

    this.inertiaStatus = {
      active: false,
      smoothEnd: false,
      ending: false,

      startEvent: null,
      upCoords: {},

      xe: 0, ye: 0,
      sx: 0, sy: 0,

      t0: 0,
      vx0: 0, vys: 0,
      duration: 0,

      resumeDx: 0,
      resumeDy: 0,

      lambda_v0: 0,
      one_ve_v0: 0,
      i: null
    };

    if (isFunction(Function.prototype.bind)){
      this.boundInertiaFrame = this.inertiaFrame.bind(this);
      this.boundSmoothEndFrame = this.smoothEndFrame.bind(this);
    }
    else {
      var that = this;

      this.boundInertiaFrame = function(){ return that.inertiaFrame(); };
      this.boundSmoothEndFrame = function(){ return that.smoothEndFrame(); };
    }

    this.activeDrops = {
      dropzones: [],      // the dropzones that are mentioned below
      elements: [],      // elements of dropzones that accept the target draggable
      rects: []       // the rects of the elements mentioned above
    };

        // keep track of added pointers
    this.pointers = [];
    this.pointerIds = [];
    this.downTargets = [];
    this.downTimes = [];
    this.holdTimers = [];

        // Previous native pointer move event coordinates
    this.prevCoords = {
      page: { x: 0, y: 0 },
      client: { x: 0, y: 0 },
      timeStamp: 0
    };
        // current native pointer move event coordinates
    this.curCoords = {
      page: { x: 0, y: 0 },
      client: { x: 0, y: 0 },
      timeStamp: 0
    };

        // Starting InteractEvent pointer coordinates
    this.startCoords = {
      page: { x: 0, y: 0 },
      client: { x: 0, y: 0 },
      timeStamp: 0
    };

        // Change in coordinates and time of the pointer
    this.pointerDelta = {
      page: { x: 0, y: 0, vx: 0, vy: 0, speed: 0 },
      client: { x: 0, y: 0, vx: 0, vy: 0, speed: 0 },
      timeStamp: 0
    };

    this.downEvent = null;    // pointerdown/mousedown/touchstart event
    this.downPointer = {};

    this._eventTarget = null;
    this._curEventTarget = null;

    this.prevEvent = null;      // previous action event
    this.tapTime = 0;         // time of the most recent tap event
    this.prevTap = null;

    this.startOffset = { left: 0, right: 0, top: 0, bottom: 0 };
    this.restrictOffset = { left: 0, right: 0, top: 0, bottom: 0 };
    this.snapOffsets = [];

    this.gesture = {
      start: { x: 0, y: 0 },

      startDistance: 0,   // distance between two touches of touchStart
      prevDistance: 0,
      distance: 0,

      scale: 1,           // gesture.distance / gesture.startDistance

      startAngle: 0,      // angle of line joining two touches
      prevAngle: 0       // angle of the previous gesture event
    };

    this.snapStatus = {
      x: 0, y: 0,
      dx: 0, dy: 0,
      realX: 0, realY: 0,
      snappedX: 0, snappedY: 0,
      targets: [],
      locked: false,
      changed: false
    };

    this.restrictStatus = {
      dx: 0, dy: 0,
      restrictedX: 0, restrictedY: 0,
      snap: null,
      restricted: false,
      changed: false
    };

    this.restrictStatus.snap = this.snapStatus;

    this.pointerIsDown = false;
    this.pointerWasMoved = false;
    this.gesturing = false;
    this.dragging = false;
    this.resizing = false;
    this.resizeAxes = 'xy';

    this.mouse = false;

    interactions.push(this);
  }

  Interaction.prototype = {
    getPageXY: function(pointer, xy){ return getPageXY(pointer, xy, this); },
    getClientXY: function(pointer, xy){ return getClientXY(pointer, xy, this); },
    setEventXY: function(target, ptr){ return setEventXY(target, ptr, this); },

    pointerOver: function(pointer, event, eventTarget){
      if (this.prepared.name || !this.mouse){ return; }

      var curMatches = [],
        curMatchElements = [],
        prevTargetElement = this.element;

      this.addPointer(pointer);

      if (this.target
                && (testIgnore(this.target, this.element, eventTarget)
                    || !testAllow(this.target, this.element, eventTarget))){
                // if the eventTarget should be ignored or shouldn't be allowed
                // clear the previous target
        this.target = null;
        this.element = null;
        this.matches = [];
        this.matchElements = [];
      }

      var elementInteractable = interactables.get(eventTarget),
        elementAction = (elementInteractable
                                 && !testIgnore(elementInteractable, eventTarget, eventTarget)
                                 && testAllow(elementInteractable, eventTarget, eventTarget)
                                 && validateAction(
                                     elementInteractable.getAction(pointer, event, this, eventTarget),
                                     elementInteractable));

      if (elementAction && !withinInteractionLimit(elementInteractable, eventTarget, elementAction)){
        elementAction = null;
      }

      function pushCurMatches(interactable, selector){
        if (interactable
                    && inContext(interactable, eventTarget)
                    && !testIgnore(interactable, eventTarget, eventTarget)
                    && testAllow(interactable, eventTarget, eventTarget)
                    && matchesSelector(eventTarget, selector)){
          curMatches.push(interactable);
          curMatchElements.push(eventTarget);
        }
      }

      if (elementAction){
        this.target = elementInteractable;
        this.element = eventTarget;
        this.matches = [];
        this.matchElements = [];
      }
      else {
        interactables.forEachSelector(pushCurMatches);

        if (this.validateSelector(pointer, event, curMatches, curMatchElements)){
          this.matches = curMatches;
          this.matchElements = curMatchElements;

          this.pointerHover(pointer, event, this.matches, this.matchElements);
          events.add(eventTarget, supportsPointerEvent ?
              pEventTypes.move :
              'mousemove',
            listeners.pointerHover);
        }
        else if (this.target){
          if (nodeContains(prevTargetElement, eventTarget)){
            this.pointerHover(pointer, event, this.matches, this.matchElements);
            events.add(this.element, supportsPointerEvent ?
                pEventTypes.move :
                'mousemove',
              listeners.pointerHover);
          }
          else {
            this.target = null;
            this.element = null;
            this.matches = [];
            this.matchElements = [];
          }
        }
      }
    },

        // Check what action would be performed on pointerMove target if a mouse
        // button were pressed and change the cursor accordingly
    pointerHover: function(pointer, event, eventTarget, curEventTarget, matches, matchElements){
      var target = this.target;

      if (!this.prepared.name && this.mouse){
        var action;

                // update pointer coords for defaultActionChecker to use
        this.setEventXY(this.curCoords, [pointer]);

        if (matches){
          action = this.validateSelector(pointer, event, matches, matchElements);
        }
        else if (target){
          action = validateAction(target.getAction(this.pointers[0], event, this, this.element), this.target);
        }

        if (target && target.options.styleCursor){
          if (action){
            target._doc.documentElement.style.cursor = getActionCursor(action);
          }
          else {
            target._doc.documentElement.style.cursor = '';
          }
        }
      }
      else if (this.prepared.name){
        this.checkAndPreventDefault(event, target, this.element);
      }
    },

    pointerOut: function(pointer, event, eventTarget){
      if (this.prepared.name){ return; }

            // Remove temporary event listeners for selector Interactables
      if (!interactables.get(eventTarget)){
        events.remove(eventTarget,
                                       supportsPointerEvent ? pEventTypes.move : 'mousemove',
                                       listeners.pointerHover);
      }

      if (this.target && this.target.options.styleCursor && !this.interacting()){
        this.target._doc.documentElement.style.cursor = '';
      }
    },

    selectorDown: function(pointer, event, eventTarget, curEventTarget){
      var that = this,
                // copy event to be used in timeout for IE8
        eventCopy = events.useAttachEvent ? extend({}, event) : event,
        element = eventTarget,
        pointerIndex = this.addPointer(pointer),
        action;

      this.holdTimers[pointerIndex] = setTimeout(function(){
        that.pointerHold(events.useAttachEvent ? eventCopy : pointer, eventCopy, eventTarget, curEventTarget);
      }, defaultOptions._holdDuration);

      this.pointerIsDown = true;

            // Check if the down event hits the current inertia target
      if (this.inertiaStatus.active && this.target.selector){
                // climb up the DOM tree from the event target
        while (isElement(element)){
                    // if this element is the current inertia target element
          if (element === this.element
                        // and the prospective action is the same as the ongoing one
                        && validateAction(this.target.getAction(pointer, event, this, this.element), this.target).name === this.prepared.name){
                        // stop inertia so that the next move will be a normal one
            cancelFrame(this.inertiaStatus.i);
            this.inertiaStatus.active = false;

            this.collectEventTargets(pointer, event, eventTarget, 'down');
            return;
          }
          element = parentElement(element);
        }
      }

            // do nothing if interacting
      if (this.interacting()){
        this.collectEventTargets(pointer, event, eventTarget, 'down');
        return;
      }

      function pushMatches(interactable, selector, context){
        var elements = ie8MatchesSelector
                    ? context.querySelectorAll(selector)
                    : undefined;

        if (inContext(interactable, element)
                    && !testIgnore(interactable, element, eventTarget)
                    && testAllow(interactable, element, eventTarget)
                    && matchesSelector(element, selector, elements)){
          that.matches.push(interactable);
          that.matchElements.push(element);
        }
      }

            // update pointer coords for defaultActionChecker to use
      this.setEventXY(this.curCoords, [pointer]);
      this.downEvent = event;

      while (isElement(element) && !action){
        this.matches = [];
        this.matchElements = [];

        interactables.forEachSelector(pushMatches);

        action = this.validateSelector(pointer, event, this.matches, this.matchElements);
        element = parentElement(element);
      }

      if (action){
        this.prepared.name = action.name;
        this.prepared.axis = action.axis;
        this.prepared.edges = action.edges;

        this.collectEventTargets(pointer, event, eventTarget, 'down');

        return this.pointerDown(pointer, event, eventTarget, curEventTarget, action);
      }
      else {
                // do these now since pointerDown isn't being called from here
        this.downTimes[pointerIndex] = new Date().getTime();
        this.downTargets[pointerIndex] = eventTarget;
        pointerExtend(this.downPointer, pointer);

        copyCoords(this.prevCoords, this.curCoords);
        this.pointerWasMoved = false;
      }

      this.collectEventTargets(pointer, event, eventTarget, 'down');
    },

        // Determine action to be performed on next pointerMove and add appropriate
        // style and event Listeners
    pointerDown: function(pointer, event, eventTarget, curEventTarget, forceAction){
      if (!forceAction && !this.inertiaStatus.active && this.pointerWasMoved && this.prepared.name){
        this.checkAndPreventDefault(event, this.target, this.element);

        return;
      }

      this.pointerIsDown = true;
      this.downEvent = event;

      var pointerIndex = this.addPointer(pointer),
        action;

            // If it is the second touch of a multi-touch gesture, keep the
            // target the same and get a new action if a target was set by the
            // first touch
      if (this.pointerIds.length > 1 && this.target._element === this.element){
        var newAction = validateAction(forceAction || this.target.getAction(pointer, event, this, this.element), this.target);

        if (withinInteractionLimit(this.target, this.element, newAction)){
          action = newAction;
        }

        this.prepared.name = null;
      }
            // Otherwise, set the target if there is no action prepared
      else if (!this.prepared.name){
        var interactable = interactables.get(curEventTarget);

        if (interactable
                    && !testIgnore(interactable, curEventTarget, eventTarget)
                    && testAllow(interactable, curEventTarget, eventTarget)
                    && (action = validateAction(forceAction || interactable.getAction(pointer, event, this, curEventTarget), interactable, eventTarget))
                    && withinInteractionLimit(interactable, curEventTarget, action)){
          this.target = interactable;
          this.element = curEventTarget;
        }
      }

      var target = this.target,
        options = target && target.options;

      if (target && (forceAction || !this.prepared.name)){
        action = action || validateAction(forceAction || target.getAction(pointer, event, this, curEventTarget), target, this.element);

        this.setEventXY(this.startCoords, this.pointers);

        if (!action){ return; }

        if (options.styleCursor){
          target._doc.documentElement.style.cursor = getActionCursor(action);
        }

        this.resizeAxes = action.name === 'resize' ? action.axis : null;

        if (action === 'gesture' && this.pointerIds.length < 2){
          action = null;
        }

        this.prepared.name = action.name;
        this.prepared.axis = action.axis;
        this.prepared.edges = action.edges;

        this.snapStatus.snappedX = this.snapStatus.snappedY =
                    this.restrictStatus.restrictedX = this.restrictStatus.restrictedY = NaN;

        this.downTimes[pointerIndex] = new Date().getTime();
        this.downTargets[pointerIndex] = eventTarget;
        pointerExtend(this.downPointer, pointer);

        copyCoords(this.prevCoords, this.startCoords);
        this.pointerWasMoved = false;

        this.checkAndPreventDefault(event, target, this.element);
      }
            // if inertia is active try to resume action
      else if (this.inertiaStatus.active
                && curEventTarget === this.element
                && validateAction(target.getAction(pointer, event, this, this.element), target).name === this.prepared.name){
        cancelFrame(this.inertiaStatus.i);
        this.inertiaStatus.active = false;

        this.checkAndPreventDefault(event, target, this.element);
      }
    },

    setModifications: function(coords, preEnd){
      var target = this.target,
        shouldMove = true,
        shouldSnap = checkSnap(target, this.prepared.name) && (!target.options[this.prepared.name].snap.endOnly || preEnd),
        shouldRestrict = checkRestrict(target, this.prepared.name) && (!target.options[this.prepared.name].restrict.endOnly || preEnd);

      if (shouldSnap){ this.setSnapping(coords); } else { this.snapStatus.locked = false; }
      if (shouldRestrict){ this.setRestriction(coords); } else { this.restrictStatus.restricted = false; }

      if (shouldSnap && this.snapStatus.locked && !this.snapStatus.changed){
        shouldMove = shouldRestrict && this.restrictStatus.restricted && this.restrictStatus.changed;
      }
      else if (shouldRestrict && this.restrictStatus.restricted && !this.restrictStatus.changed){
        shouldMove = false;
      }

      return shouldMove;
    },

    setStartOffsets: function(action, interactable, element){
      var rect = interactable.getRect(element),
        origin = getOriginXY(interactable, element),
        snap = interactable.options[this.prepared.name].snap,
        restrict = interactable.options[this.prepared.name].restrict,
        width, height;

      if (rect){
        this.startOffset.left = this.startCoords.page.x - rect.left;
        this.startOffset.top = this.startCoords.page.y - rect.top;

        this.startOffset.right = rect.right - this.startCoords.page.x;
        this.startOffset.bottom = rect.bottom - this.startCoords.page.y;

        if ('width' in rect){ width = rect.width; }
        else { width = rect.right - rect.left; }
        if ('height' in rect){ height = rect.height; }
        else { height = rect.bottom - rect.top; }
      }
      else {
        this.startOffset.left = this.startOffset.top = this.startOffset.right = this.startOffset.bottom = 0;
      }

      this.snapOffsets.splice(0);

      var snapOffset = snap && snap.offset === 'startCoords'
                                ? {
                                  x: this.startCoords.page.x - origin.x,
                                  y: this.startCoords.page.y - origin.y
                                }
                                : snap && snap.offset || { x: 0, y: 0 };

      if (rect && snap && snap.relativePoints && snap.relativePoints.length){
        for (var i = 0; i < snap.relativePoints.length; i++){
          this.snapOffsets.push({
            x: this.startOffset.left - (width * snap.relativePoints[i].x) + snapOffset.x,
            y: this.startOffset.top - (height * snap.relativePoints[i].y) + snapOffset.y
          });
        }
      }
      else {
        this.snapOffsets.push(snapOffset);
      }

      if (rect && restrict.elementRect){
        this.restrictOffset.left = this.startOffset.left - (width * restrict.elementRect.left);
        this.restrictOffset.top = this.startOffset.top - (height * restrict.elementRect.top);

        this.restrictOffset.right = this.startOffset.right - (width * (1 - restrict.elementRect.right));
        this.restrictOffset.bottom = this.startOffset.bottom - (height * (1 - restrict.elementRect.bottom));
      }
      else {
        this.restrictOffset.left = this.restrictOffset.top = this.restrictOffset.right = this.restrictOffset.bottom = 0;
      }
    },

        /* \
         * Interaction.start
         [ method ]
         *
         * Start an action with the given Interactable and Element as tartgets. The
         * action must be enabled for the target Interactable and an appropriate number
         * of pointers must be held down – 1 for drag/resize, 2 for gesture.
         *
         * Use it with `interactable.<action>able({ manualStart: false })` to always
         * [start actions manually](https://github.com/taye/interact.js/issues/114)
         *
         - action       (object)  The action to be performed - drag, resize, etc.
         - interactable (Interactable) The Interactable to target
         - element      (Element) The DOM Element to target
         = (object) interact
         **
         | interact(target)
         |   .draggable({
         |     // disable the default drag start by down->move
         |     manualStart: true
         |   })
         |   // start dragging after the user holds the pointer down
         |   .on('hold', function (event) {
         |     var interaction = event.interaction;
         |
         |     if (!interaction.interacting()) {
         |       interaction.start({ name: 'drag' },
         |                         event.interactable,
         |                         event.currentTarget);
         |     }
         | });
        \*/
    start: function(action, interactable, element){
      if (this.interacting()
                || !this.pointerIsDown
                || this.pointerIds.length < (action.name === 'gesture' ? 2 : 1)){
        return;
      }

            // if this interaction had been removed after stopping
            // add it back
      if (indexOf(interactions, this) === -1){
        interactions.push(this);
      }

            // set the startCoords if there was no prepared action
      if (!this.prepared.name){
        this.setEventXY(this.startCoords);
      }

      this.prepared.name = action.name;
      this.prepared.axis = action.axis;
      this.prepared.edges = action.edges;
      this.target = interactable;
      this.element = element;

      this.setStartOffsets(action.name, interactable, element);
      this.setModifications(this.startCoords.page);

      this.prevEvent = this[this.prepared.name + 'Start'](this.downEvent);
    },

    pointerMove: function(pointer, event, eventTarget, curEventTarget, preEnd){
      if (this.inertiaStatus.active){
        var pageUp = this.inertiaStatus.upCoords.page;
        var clientUp = this.inertiaStatus.upCoords.client;

        var inertiaPosition = {
          pageX: pageUp.x + this.inertiaStatus.sx,
          pageY: pageUp.y + this.inertiaStatus.sy,
          clientX: clientUp.x + this.inertiaStatus.sx,
          clientY: clientUp.y + this.inertiaStatus.sy
        };

        this.setEventXY(this.curCoords, [inertiaPosition]);
      }
      else {
        this.recordPointer(pointer);
        this.setEventXY(this.curCoords, this.pointers);
      }

      var duplicateMove = (this.curCoords.page.x === this.prevCoords.page.x
                                 && this.curCoords.page.y === this.prevCoords.page.y
                                 && this.curCoords.client.x === this.prevCoords.client.x
                                 && this.curCoords.client.y === this.prevCoords.client.y);

      var dx, dy,
        pointerIndex = this.mouse ? 0 : indexOf(this.pointerIds, getPointerId(pointer));

            // register movement greater than pointerMoveTolerance
      if (this.pointerIsDown && !this.pointerWasMoved){
        dx = this.curCoords.client.x - this.startCoords.client.x;
        dy = this.curCoords.client.y - this.startCoords.client.y;

        this.pointerWasMoved = hypot(dx, dy) > pointerMoveTolerance;
      }

      if (!duplicateMove && (!this.pointerIsDown || this.pointerWasMoved)){
        if (this.pointerIsDown){
          clearTimeout(this.holdTimers[pointerIndex]);
        }

        this.collectEventTargets(pointer, event, eventTarget, 'move');
      }

      if (!this.pointerIsDown){ return; }

      if (duplicateMove && this.pointerWasMoved && !preEnd){
        this.checkAndPreventDefault(event, this.target, this.element);
        return;
      }

            // set pointer coordinate, time changes and speeds
      setEventDeltas(this.pointerDelta, this.prevCoords, this.curCoords);

      if (!this.prepared.name){ return; }

      if (this.pointerWasMoved
                // ignore movement while inertia is active
                && (!this.inertiaStatus.active || (pointer instanceof InteractEvent && /inertiastart/.test(pointer.type)))){
                // if just starting an action, calculate the pointer speed now
        if (!this.interacting()){
          setEventDeltas(this.pointerDelta, this.prevCoords, this.curCoords);

                    // check if a drag is in the correct axis
          if (this.prepared.name === 'drag'){
            var absX = Math.abs(dx),
              absY = Math.abs(dy),
              targetAxis = this.target.options.drag.axis,
              axis = (absX > absY ? 'x' : absX < absY ? 'y' : 'xy');

                        // if the movement isn't in the axis of the interactable
            if (axis !== 'xy' && targetAxis !== 'xy' && targetAxis !== axis){
                            // cancel the prepared action
              this.prepared.name = null;

                            // then try to get a drag from another ineractable

              var element = eventTarget;

                            // check element interactables
              while (isElement(element)){
                var elementInteractable = interactables.get(element);

                if (elementInteractable
                                    && elementInteractable !== this.target
                                    && !elementInteractable.options.drag.manualStart
                                    && elementInteractable.getAction(this.downPointer, this.downEvent, this, element).name === 'drag'
                                    && checkAxis(axis, elementInteractable)){
                  this.prepared.name = 'drag';
                  this.target = elementInteractable;
                  this.element = element;
                  break;
                }

                element = parentElement(element);
              }

                            // if there's no drag from element interactables,
                            // check the selector interactables
              if (!this.prepared.name){
                var thisInteraction = this;

                var getDraggable = function(interactable, selector, context){
                  var elements = ie8MatchesSelector
                                        ? context.querySelectorAll(selector)
                                        : undefined;

                  if (interactable === thisInteraction.target){ return; }

                  if (inContext(interactable, eventTarget)
                                        && !interactable.options.drag.manualStart
                                        && !testIgnore(interactable, element, eventTarget)
                                        && testAllow(interactable, element, eventTarget)
                                        && matchesSelector(element, selector, elements)
                                        && interactable.getAction(thisInteraction.downPointer, thisInteraction.downEvent, thisInteraction, element).name === 'drag'
                                        && checkAxis(axis, interactable)
                                        && withinInteractionLimit(interactable, element, 'drag')){
                    return interactable;
                  }
                };

                element = eventTarget;

                while (isElement(element)){
                  var selectorInteractable = interactables.forEachSelector(getDraggable);

                  if (selectorInteractable){
                    this.prepared.name = 'drag';
                    this.target = selectorInteractable;
                    this.element = element;
                    break;
                  }

                  element = parentElement(element);
                }
              }
            }
          }
        }

        var starting = !!this.prepared.name && !this.interacting();

        if (starting
                    && (this.target.options[this.prepared.name].manualStart
                        || !withinInteractionLimit(this.target, this.element, this.prepared))){
          this.stop(event);
          return;
        }

        if (this.prepared.name && this.target){
          if (starting){
            this.start(this.prepared, this.target, this.element);
          }

          var shouldMove = this.setModifications(this.curCoords.page, preEnd);

                    // move if snapping or restriction doesn't prevent it
          if (shouldMove || starting){
            this.prevEvent = this[this.prepared.name + 'Move'](event);
          }

          this.checkAndPreventDefault(event, this.target, this.element);
        }
      }

      copyCoords(this.prevCoords, this.curCoords);

      if (this.dragging || this.resizing){
        this.autoScrollMove(pointer);
      }
    },

    dragStart: function(event){
      var dragEvent = new InteractEvent(this, event, 'drag', 'start', this.element);

      dragEvent.originalEvent = event;
      this.dragging = true;
      this.target.fire(dragEvent);

            // reset active dropzones
      this.activeDrops.dropzones = [];
      this.activeDrops.elements = [];
      this.activeDrops.rects = [];

      if (!this.dynamicDrop){
        this.setActiveDrops(this.element);
      }

      var dropEvents = this.getDropEvents(event, dragEvent);

      if (dropEvents.activate){
        this.fireActiveDrops(dropEvents.activate);
      }

      return dragEvent;
    },

    dragMove: function(event){
      var target = this.target,
        dragEvent = new InteractEvent(this, event, 'drag', 'move', this.element),
        draggableElement = this.element,
        drop = this.getDrop(dragEvent, event, draggableElement);

      dragEvent.originalEvent = event;

      this.dropTarget = drop.dropzone;
      this.dropElement = drop.element;

      var dropEvents = this.getDropEvents(event, dragEvent);

      target.fire(dragEvent);

      if (dropEvents.leave){ this.prevDropTarget.fire(dropEvents.leave); }
      if (dropEvents.enter){ this.dropTarget.fire(dropEvents.enter); }
      if (dropEvents.move){ this.dropTarget.fire(dropEvents.move); }

      this.prevDropTarget = this.dropTarget;
      this.prevDropElement = this.dropElement;

      return dragEvent;
    },

    resizeStart: function(event){
      var resizeEvent = new InteractEvent(this, event, 'resize', 'start', this.element);

      if (this.prepared.edges){
        var startRect = this.target.getRect(this.element);

                /*
                 * When using the `resizable.square` or `resizable.preserveAspectRatio` options, resizing from one edge
                 * will affect another. E.g. with `resizable.square`, resizing to make the right edge larger will make
                 * the bottom edge larger by the same amount. We call these 'linked' edges. Any linked edges will depend
                 * on the active edges and the edge being interacted with.
                 */
        if (this.target.options.resize.square || this.target.options.resize.preserveAspectRatio){
          var linkedEdges = extend({}, this.prepared.edges);

          linkedEdges.top = linkedEdges.top || (linkedEdges.left && !linkedEdges.bottom);
          linkedEdges.left = linkedEdges.left || (linkedEdges.top && !linkedEdges.right);
          linkedEdges.bottom = linkedEdges.bottom || (linkedEdges.right && !linkedEdges.top);
          linkedEdges.right = linkedEdges.right || (linkedEdges.bottom && !linkedEdges.left);

          this.prepared._linkedEdges = linkedEdges;
        }
        else {
          this.prepared._linkedEdges = null;
        }

                // if using `resizable.preserveAspectRatio` option, record aspect ratio at the start of the resize
        if (this.target.options.resize.preserveAspectRatio){
          this.resizeStartAspectRatio = startRect.width / startRect.height;
        }

        this.resizeRects = {
          start: startRect,
          current: extend({}, startRect),
          restricted: extend({}, startRect),
          previous: extend({}, startRect),
          delta: {
            left: 0, right: 0, width: 0,
            top: 0, bottom: 0, height: 0
          }
        };

        resizeEvent.rect = this.resizeRects.restricted;
        resizeEvent.deltaRect = this.resizeRects.delta;
      }

      this.target.fire(resizeEvent);

      this.resizing = true;

      return resizeEvent;
    },

    resizeMove: function(event){
      var resizeEvent = new InteractEvent(this, event, 'resize', 'move', this.element);

      var edges = this.prepared.edges,
        invert = this.target.options.resize.invert,
        invertible = invert === 'reposition' || invert === 'negate';

      if (edges){
        var dx = resizeEvent.dx,
          dy = resizeEvent.dy,

          start = this.resizeRects.start,
          current = this.resizeRects.current,
          restricted = this.resizeRects.restricted,
          delta = this.resizeRects.delta,
          previous = extend(this.resizeRects.previous, restricted),

          originalEdges = edges;

                // `resize.preserveAspectRatio` takes precedence over `resize.square`
        if (this.target.options.resize.preserveAspectRatio){
          var resizeStartAspectRatio = this.resizeStartAspectRatio;

          edges = this.prepared._linkedEdges;

          if ((originalEdges.left && originalEdges.bottom)
                        || (originalEdges.right && originalEdges.top)){
            dy = -dx / resizeStartAspectRatio;
          }
          else if (originalEdges.left || originalEdges.right){ dy = dx / resizeStartAspectRatio; }
                    else if (originalEdges.top || originalEdges.bottom){ dx = dy * resizeStartAspectRatio; }
        }
        else if (this.target.options.resize.square){
          edges = this.prepared._linkedEdges;

          if ((originalEdges.left && originalEdges.bottom)
                        || (originalEdges.right && originalEdges.top)){
            dy = -dx;
          }
          else if (originalEdges.left || originalEdges.right){ dy = dx; }
                    else if (originalEdges.top || originalEdges.bottom){ dx = dy; }
        }

                // update the 'current' rect without modifications
        if (edges.top){ current.top += dy; }
        if (edges.bottom){ current.bottom += dy; }
        if (edges.left){ current.left += dx; }
        if (edges.right){ current.right += dx; }

        if (invertible){
                    // if invertible, copy the current rect
          extend(restricted, current);

          if (invert === 'reposition'){
                        // swap edge values if necessary to keep width/height positive
            var swap;

            if (restricted.top > restricted.bottom){
              swap = restricted.top;

              restricted.top = restricted.bottom;
              restricted.bottom = swap;
            }
            if (restricted.left > restricted.right){
              swap = restricted.left;

              restricted.left = restricted.right;
              restricted.right = swap;
            }
          }
        }
        else {
                    // if not invertible, restrict to minimum of 0x0 rect
          restricted.top = Math.min(current.top, start.bottom);
          restricted.bottom = Math.max(current.bottom, start.top);
          restricted.left = Math.min(current.left, start.right);
          restricted.right = Math.max(current.right, start.left);
        }

        restricted.width = restricted.right - restricted.left;
        restricted.height = restricted.bottom - restricted.top;

        for (var edge in restricted){
          delta[edge] = restricted[edge] - previous[edge];
        }

        resizeEvent.edges = this.prepared.edges;
        resizeEvent.rect = restricted;
        resizeEvent.deltaRect = delta;
      }

      this.target.fire(resizeEvent);

      return resizeEvent;
    },

    gestureStart: function(event){
      var gestureEvent = new InteractEvent(this, event, 'gesture', 'start', this.element);

      gestureEvent.ds = 0;

      this.gesture.startDistance = this.gesture.prevDistance = gestureEvent.distance;
      this.gesture.startAngle = this.gesture.prevAngle = gestureEvent.angle;
      this.gesture.scale = 1;

      this.gesturing = true;

      this.target.fire(gestureEvent);

      return gestureEvent;
    },

    gestureMove: function(event){
      if (!this.pointerIds.length){
        return this.prevEvent;
      }

      var gestureEvent;

      gestureEvent = new InteractEvent(this, event, 'gesture', 'move', this.element);
      gestureEvent.ds = gestureEvent.scale - this.gesture.scale;

      this.target.fire(gestureEvent);

      this.gesture.prevAngle = gestureEvent.angle;
      this.gesture.prevDistance = gestureEvent.distance;

      if (gestureEvent.scale !== Infinity &&
                gestureEvent.scale !== null &&
                gestureEvent.scale !== undefined &&
                !isNaN(gestureEvent.scale)){
        this.gesture.scale = gestureEvent.scale;
      }

      return gestureEvent;
    },

    pointerHold: function(pointer, event, eventTarget){
      this.collectEventTargets(pointer, event, eventTarget, 'hold');
    },

    pointerUp: function(pointer, event, eventTarget, curEventTarget){
      var pointerIndex = this.mouse ? 0 : indexOf(this.pointerIds, getPointerId(pointer));

      clearTimeout(this.holdTimers[pointerIndex]);

      this.collectEventTargets(pointer, event, eventTarget, 'up');
      this.collectEventTargets(pointer, event, eventTarget, 'tap');

      this.pointerEnd(pointer, event, eventTarget, curEventTarget);

      this.removePointer(pointer);
    },

    pointerCancel: function(pointer, event, eventTarget, curEventTarget){
      var pointerIndex = this.mouse ? 0 : indexOf(this.pointerIds, getPointerId(pointer));

      clearTimeout(this.holdTimers[pointerIndex]);

      this.collectEventTargets(pointer, event, eventTarget, 'cancel');
      this.pointerEnd(pointer, event, eventTarget, curEventTarget);

      this.removePointer(pointer);
    },

        // http://www.quirksmode.org/dom/events/click.html
        // >Events leading to dblclick
        //
        // IE8 doesn't fire down event before dblclick.
        // This workaround tries to fire a tap and doubletap after dblclick
    ie8Dblclick: function(pointer, event, eventTarget){
      if (this.prevTap
                && event.clientX === this.prevTap.clientX
                && event.clientY === this.prevTap.clientY
                && eventTarget === this.prevTap.target){
        this.downTargets[0] = eventTarget;
        this.downTimes[0] = new Date().getTime();
        this.collectEventTargets(pointer, event, eventTarget, 'tap');
      }
    },

        // End interact move events and stop auto-scroll unless inertia is enabled
    pointerEnd: function(pointer, event, eventTarget, curEventTarget){
      var endEvent,
        target = this.target,
        options = target && target.options,
        inertiaOptions = options && this.prepared.name && options[this.prepared.name].inertia,
        inertiaStatus = this.inertiaStatus;

      if (this.interacting()){
        if (inertiaStatus.active && !inertiaStatus.ending){ return; }

        var pointerSpeed,
          now = new Date().getTime(),
          inertiaPossible = false,
          inertia = false,
          smoothEnd = false,
          endSnap = checkSnap(target, this.prepared.name) && options[this.prepared.name].snap.endOnly,
          endRestrict = checkRestrict(target, this.prepared.name) && options[this.prepared.name].restrict.endOnly,
          dx = 0,
          dy = 0,
          startEvent;

        if (this.dragging){
          if (options.drag.axis === 'x'){ pointerSpeed = Math.abs(this.pointerDelta.client.vx); }
          else if (options.drag.axis === 'y'){ pointerSpeed = Math.abs(this.pointerDelta.client.vy); }
                    else   /* options.drag.axis === 'xy'*/{ pointerSpeed = this.pointerDelta.client.speed; }
        }
        else {
          pointerSpeed = this.pointerDelta.client.speed;
        }

                // check if inertia should be started
        inertiaPossible = (inertiaOptions && inertiaOptions.enabled
                                   && this.prepared.name !== 'gesture'
                                   && event !== inertiaStatus.startEvent);

        inertia = (inertiaPossible
                           && (now - this.curCoords.timeStamp) < 50
                           && pointerSpeed > inertiaOptions.minSpeed
                           && pointerSpeed > inertiaOptions.endSpeed);

        if (inertiaPossible && !inertia && (endSnap || endRestrict)){
          var snapRestrict = {};

          snapRestrict.snap = snapRestrict.restrict = snapRestrict;

          if (endSnap){
            this.setSnapping(this.curCoords.page, snapRestrict);
            if (snapRestrict.locked){
              dx += snapRestrict.dx;
              dy += snapRestrict.dy;
            }
          }

          if (endRestrict){
            this.setRestriction(this.curCoords.page, snapRestrict);
            if (snapRestrict.restricted){
              dx += snapRestrict.dx;
              dy += snapRestrict.dy;
            }
          }

          if (dx || dy){
            smoothEnd = true;
          }
        }

        if (inertia || smoothEnd){
          copyCoords(inertiaStatus.upCoords, this.curCoords);

          this.pointers[0] = inertiaStatus.startEvent = startEvent =
                        new InteractEvent(this, event, this.prepared.name, 'inertiastart', this.element);

          inertiaStatus.t0 = now;

          target.fire(inertiaStatus.startEvent);

          if (inertia){
            inertiaStatus.vx0 = this.pointerDelta.client.vx;
            inertiaStatus.vy0 = this.pointerDelta.client.vy;
            inertiaStatus.v0 = pointerSpeed;

            this.calcInertia(inertiaStatus);

            var page = extend({}, this.curCoords.page),
              origin = getOriginXY(target, this.element),
              statusObject;

            page.x = page.x + inertiaStatus.xe - origin.x;
            page.y = page.y + inertiaStatus.ye - origin.y;

            statusObject = {
              useStatusXY: true,
              x: page.x,
              y: page.y,
              dx: 0,
              dy: 0,
              snap: null
            };

            statusObject.snap = statusObject;

            dx = dy = 0;

            if (endSnap){
              var snap = this.setSnapping(this.curCoords.page, statusObject);

              if (snap.locked){
                dx += snap.dx;
                dy += snap.dy;
              }
            }

            if (endRestrict){
              var restrict = this.setRestriction(this.curCoords.page, statusObject);

              if (restrict.restricted){
                dx += restrict.dx;
                dy += restrict.dy;
              }
            }

            inertiaStatus.modifiedXe += dx;
            inertiaStatus.modifiedYe += dy;

            inertiaStatus.i = reqFrame(this.boundInertiaFrame);
          }
          else {
            inertiaStatus.smoothEnd = true;
            inertiaStatus.xe = dx;
            inertiaStatus.ye = dy;

            inertiaStatus.sx = inertiaStatus.sy = 0;

            inertiaStatus.i = reqFrame(this.boundSmoothEndFrame);
          }

          inertiaStatus.active = true;
          return;
        }

        if (endSnap || endRestrict){
                    // fire a move event at the snapped coordinates
          this.pointerMove(pointer, event, eventTarget, curEventTarget, true);
        }
      }

      if (this.dragging){
        endEvent = new InteractEvent(this, event, 'drag', 'end', this.element);

        var draggableElement = this.element,
          drop = this.getDrop(endEvent, event, draggableElement);

        this.dropTarget = drop.dropzone;
        this.dropElement = drop.element;

        var dropEvents = this.getDropEvents(event, endEvent);

        if (dropEvents.leave){ this.prevDropTarget.fire(dropEvents.leave); }
        if (dropEvents.enter){ this.dropTarget.fire(dropEvents.enter); }
        if (dropEvents.drop){ this.dropTarget.fire(dropEvents.drop); }
        if (dropEvents.deactivate){
          this.fireActiveDrops(dropEvents.deactivate);
        }

        target.fire(endEvent);
      }
      else if (this.resizing){
        endEvent = new InteractEvent(this, event, 'resize', 'end', this.element);
        target.fire(endEvent);
      }
            else if (this.gesturing){
              endEvent = new InteractEvent(this, event, 'gesture', 'end', this.element);
              target.fire(endEvent);
            }

      this.stop(event);
    },

    collectDrops: function(element){
      var drops = [],
        elements = [],
        i;

      element = element || this.element;

            // collect all dropzones and their elements which qualify for a drop
      for (i = 0; i < interactables.length; i++){
        if (!interactables[i].options.drop.enabled){ continue; }

        var current = interactables[i],
          accept = current.options.drop.accept;

                // test the draggable element against the dropzone's accept setting
        if ((isElement(accept) && accept !== element)
                    || (isString(accept)
                        && !matchesSelector(element, accept))){
          continue;
        }

                // query for new elements if necessary
        var dropElements = current.selector ? current._context.querySelectorAll(current.selector) : [current._element];

        for (var j = 0, len = dropElements.length; j < len; j++){
          var currentElement = dropElements[j];

          if (currentElement === element){
            continue;
          }

          drops.push(current);
          elements.push(currentElement);
        }
      }

      return {
        dropzones: drops,
        elements: elements
      };
    },

    fireActiveDrops: function(event){
      var i,
        current,
        currentElement,
        prevElement;

            // loop through all active dropzones and trigger event
      for (i = 0; i < this.activeDrops.dropzones.length; i++){
        current = this.activeDrops.dropzones[i];
        currentElement = this.activeDrops.elements[i];

                // prevent trigger of duplicate events on same element
        if (currentElement !== prevElement){
                    // set current element as event target
          event.target = currentElement;
          current.fire(event);
        }
        prevElement = currentElement;
      }
    },

        // Collect a new set of possible drops and save them in activeDrops.
        // setActiveDrops should always be called when a drag has just started or a
        // drag event happens while dynamicDrop is true
    setActiveDrops: function(dragElement){
            // get dropzones and their elements that could receive the draggable
      var possibleDrops = this.collectDrops(dragElement, true);

      this.activeDrops.dropzones = possibleDrops.dropzones;
      this.activeDrops.elements = possibleDrops.elements;
      this.activeDrops.rects = [];

      for (var i = 0; i < this.activeDrops.dropzones.length; i++){
        this.activeDrops.rects[i] = this.activeDrops.dropzones[i].getRect(this.activeDrops.elements[i]);
      }
    },

    getDrop: function(dragEvent, event, dragElement){
      var validDrops = [];

      if (dynamicDrop){
        this.setActiveDrops(dragElement);
      }

            // collect all dropzones and their elements which qualify for a drop
      for (var j = 0; j < this.activeDrops.dropzones.length; j++){
        var current = this.activeDrops.dropzones[j],
          currentElement = this.activeDrops.elements[j],
          rect = this.activeDrops.rects[j];

        validDrops.push(current.dropCheck(dragEvent, event, this.target, dragElement, currentElement, rect)
                                ? currentElement
                                : null);
      }

            // get the most appropriate dropzone based on DOM depth and order
      var dropIndex = indexOfDeepestElement(validDrops),
        dropzone = this.activeDrops.dropzones[dropIndex] || null,
        element = this.activeDrops.elements[dropIndex] || null;

      return {
        dropzone: dropzone,
        element: element
      };
    },

    getDropEvents: function(pointerEvent, dragEvent){
      var dropEvents = {
        enter: null,
        leave: null,
        activate: null,
        deactivate: null,
        move: null,
        drop: null
      };

      if (this.dropElement !== this.prevDropElement){
                // if there was a prevDropTarget, create a dragleave event
        if (this.prevDropTarget){
          dropEvents.leave = {
            target: this.prevDropElement,
            dropzone: this.prevDropTarget,
            relatedTarget: dragEvent.target,
            draggable: dragEvent.interactable,
            dragEvent: dragEvent,
            interaction: this,
            timeStamp: dragEvent.timeStamp,
            type: 'dragleave'
          };

          dragEvent.dragLeave = this.prevDropElement;
          dragEvent.prevDropzone = this.prevDropTarget;
        }
                // if the dropTarget is not null, create a dragenter event
        if (this.dropTarget){
          dropEvents.enter = {
            target: this.dropElement,
            dropzone: this.dropTarget,
            relatedTarget: dragEvent.target,
            draggable: dragEvent.interactable,
            dragEvent: dragEvent,
            interaction: this,
            timeStamp: dragEvent.timeStamp,
            type: 'dragenter'
          };

          dragEvent.dragEnter = this.dropElement;
          dragEvent.dropzone = this.dropTarget;
        }
      }

      if (dragEvent.type === 'dragend' && this.dropTarget){
        dropEvents.drop = {
          target: this.dropElement,
          dropzone: this.dropTarget,
          relatedTarget: dragEvent.target,
          draggable: dragEvent.interactable,
          dragEvent: dragEvent,
          interaction: this,
          timeStamp: dragEvent.timeStamp,
          type: 'drop'
        };

        dragEvent.dropzone = this.dropTarget;
      }
      if (dragEvent.type === 'dragstart'){
        dropEvents.activate = {
          target: null,
          dropzone: null,
          relatedTarget: dragEvent.target,
          draggable: dragEvent.interactable,
          dragEvent: dragEvent,
          interaction: this,
          timeStamp: dragEvent.timeStamp,
          type: 'dropactivate'
        };
      }
      if (dragEvent.type === 'dragend'){
        dropEvents.deactivate = {
          target: null,
          dropzone: null,
          relatedTarget: dragEvent.target,
          draggable: dragEvent.interactable,
          dragEvent: dragEvent,
          interaction: this,
          timeStamp: dragEvent.timeStamp,
          type: 'dropdeactivate'
        };
      }
      if (dragEvent.type === 'dragmove' && this.dropTarget){
        dropEvents.move = {
          target: this.dropElement,
          dropzone: this.dropTarget,
          relatedTarget: dragEvent.target,
          draggable: dragEvent.interactable,
          dragEvent: dragEvent,
          interaction: this,
          dragmove: dragEvent,
          timeStamp: dragEvent.timeStamp,
          type: 'dropmove'
        };
        dragEvent.dropzone = this.dropTarget;
      }

      return dropEvents;
    },

    currentAction: function(){
      return (this.dragging && 'drag') || (this.resizing && 'resize') || (this.gesturing && 'gesture') || null;
    },

    interacting: function(){
      return this.dragging || this.resizing || this.gesturing;
    },

    clearTargets: function(){
      this.target = this.element = null;

      this.dropTarget = this.dropElement = this.prevDropTarget = this.prevDropElement = null;
    },

    stop: function(event){
      if (this.interacting()){
        autoScroll.stop();
        this.matches = [];
        this.matchElements = [];

        var target = this.target;

        if (target.options.styleCursor){
          target._doc.documentElement.style.cursor = '';
        }

                // prevent Default only if were previously interacting
        if (event && isFunction(event.preventDefault)){
          this.checkAndPreventDefault(event, target, this.element);
        }

        if (this.dragging){
          this.activeDrops.dropzones = this.activeDrops.elements = this.activeDrops.rects = null;
        }
      }

      this.clearTargets();

      this.pointerIsDown = this.snapStatus.locked = this.dragging = this.resizing = this.gesturing = false;
      this.prepared.name = this.prevEvent = null;
      this.inertiaStatus.resumeDx = this.inertiaStatus.resumeDy = 0;

            // remove pointers if their ID isn't in this.pointerIds
      for (var i = 0; i < this.pointers.length; i++){
        if (indexOf(this.pointerIds, getPointerId(this.pointers[i])) === -1){
          this.pointers.splice(i, 1);
        }
      }
    },

    inertiaFrame: function(){
      var inertiaStatus = this.inertiaStatus,
        options = this.target.options[this.prepared.name].inertia,
        lambda = options.resistance,
        t = new Date().getTime() / 1000 - inertiaStatus.t0;

      if (t < inertiaStatus.te){
        var progress = 1 - (Math.exp(-lambda * t) - inertiaStatus.lambda_v0) / inertiaStatus.one_ve_v0;

        if (inertiaStatus.modifiedXe === inertiaStatus.xe && inertiaStatus.modifiedYe === inertiaStatus.ye){
          inertiaStatus.sx = inertiaStatus.xe * progress;
          inertiaStatus.sy = inertiaStatus.ye * progress;
        }
        else {
          var quadPoint = getQuadraticCurvePoint(
                            0, 0,
                            inertiaStatus.xe, inertiaStatus.ye,
                            inertiaStatus.modifiedXe, inertiaStatus.modifiedYe,
                            progress);

          inertiaStatus.sx = quadPoint.x;
          inertiaStatus.sy = quadPoint.y;
        }

        this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent);

        inertiaStatus.i = reqFrame(this.boundInertiaFrame);
      }
      else {
        inertiaStatus.ending = true;

        inertiaStatus.sx = inertiaStatus.modifiedXe;
        inertiaStatus.sy = inertiaStatus.modifiedYe;

        this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent);
        this.pointerEnd(inertiaStatus.startEvent, inertiaStatus.startEvent);

        inertiaStatus.active = inertiaStatus.ending = false;
      }
    },

    smoothEndFrame: function(){
      var inertiaStatus = this.inertiaStatus,
        t = new Date().getTime() - inertiaStatus.t0,
        duration = this.target.options[this.prepared.name].inertia.smoothEndDuration;

      if (t < duration){
        inertiaStatus.sx = easeOutQuad(t, 0, inertiaStatus.xe, duration);
        inertiaStatus.sy = easeOutQuad(t, 0, inertiaStatus.ye, duration);

        this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent);

        inertiaStatus.i = reqFrame(this.boundSmoothEndFrame);
      }
      else {
        inertiaStatus.ending = true;

        inertiaStatus.sx = inertiaStatus.xe;
        inertiaStatus.sy = inertiaStatus.ye;

        this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent);
        this.pointerEnd(inertiaStatus.startEvent, inertiaStatus.startEvent);

        inertiaStatus.smoothEnd =
                  inertiaStatus.active = inertiaStatus.ending = false;
      }
    },

    addPointer: function(pointer){
      var id = getPointerId(pointer),
        index = this.mouse ? 0 : indexOf(this.pointerIds, id);

      if (index === -1){
        index = this.pointerIds.length;
      }

      this.pointerIds[index] = id;
      this.pointers[index] = pointer;

      return index;
    },

    removePointer: function(pointer){
      var id = getPointerId(pointer),
        index = this.mouse ? 0 : indexOf(this.pointerIds, id);

      if (index === -1){ return; }

      this.pointers.splice(index, 1);
      this.pointerIds.splice(index, 1);
      this.downTargets.splice(index, 1);
      this.downTimes.splice(index, 1);
      this.holdTimers.splice(index, 1);
    },

    recordPointer: function(pointer){
      var index = this.mouse ? 0 : indexOf(this.pointerIds, getPointerId(pointer));

      if (index === -1){ return; }

      this.pointers[index] = pointer;
    },

    collectEventTargets: function(pointer, event, eventTarget, eventType){
      var pointerIndex = this.mouse ? 0 : indexOf(this.pointerIds, getPointerId(pointer));

            // do not fire a tap event if the pointer was moved before being lifted
      if (eventType === 'tap' && (this.pointerWasMoved
                // or if the pointerup target is different to the pointerdown target
                || !(this.downTargets[pointerIndex] && this.downTargets[pointerIndex] === eventTarget))){
        return;
      }

      var targets = [],
        elements = [],
        element = eventTarget;

      function collectSelectors(interactable, selector, context){
        var els = ie8MatchesSelector
                        ? context.querySelectorAll(selector)
                        : undefined;

        if (interactable._iEvents[eventType]
                    && isElement(element)
                    && inContext(interactable, element)
                    && !testIgnore(interactable, element, eventTarget)
                    && testAllow(interactable, element, eventTarget)
                    && matchesSelector(element, selector, els)){
          targets.push(interactable);
          elements.push(element);
        }
      }

      while (element){
        if (interact.isSet(element) && interact(element)._iEvents[eventType]){
          targets.push(interact(element));
          elements.push(element);
        }

        interactables.forEachSelector(collectSelectors);

        element = parentElement(element);
      }

            // create the tap event even if there are no listeners so that
            // doubletap can still be created and fired
      if (targets.length || eventType === 'tap'){
        this.firePointers(pointer, event, eventTarget, targets, elements, eventType);
      }
    },

    firePointers: function(pointer, event, eventTarget, targets, elements, eventType){
      var pointerIndex = this.mouse ? 0 : indexOf(this.pointerIds, getPointerId(pointer)),
        pointerEvent = {},
        i,
                // for tap events
        interval, createNewDoubleTap;

            // if it's a doubletap then the event properties would have been
            // copied from the tap event and provided as the pointer argument
      if (eventType === 'doubletap'){
        pointerEvent = pointer;
      }
      else {
        pointerExtend(pointerEvent, event);
        if (event !== pointer){
          pointerExtend(pointerEvent, pointer);
        }

        pointerEvent.preventDefault = preventOriginalDefault;
        pointerEvent.stopPropagation = InteractEvent.prototype.stopPropagation;
        pointerEvent.stopImmediatePropagation = InteractEvent.prototype.stopImmediatePropagation;
        pointerEvent.interaction = this;

        pointerEvent.timeStamp = new Date().getTime();
        pointerEvent.originalEvent = event;
        pointerEvent.originalPointer = pointer;
        pointerEvent.type = eventType;
        pointerEvent.pointerId = getPointerId(pointer);
        pointerEvent.pointerType = this.mouse ? 'mouse' : !supportsPointerEvent ? 'touch'
                                                    : isString(pointer.pointerType)
                                                        ? pointer.pointerType
                                                        : [,, 'touch', 'pen', 'mouse'][pointer.pointerType];
      }

      if (eventType === 'tap'){
        pointerEvent.dt = pointerEvent.timeStamp - this.downTimes[pointerIndex];

        interval = pointerEvent.timeStamp - this.tapTime;
        createNewDoubleTap = !!(this.prevTap && this.prevTap.type !== 'doubletap'
                       && this.prevTap.target === pointerEvent.target
                       && interval < 500);

        pointerEvent.double = createNewDoubleTap;

        this.tapTime = pointerEvent.timeStamp;
      }

      for (i = 0; i < targets.length; i++){
        pointerEvent.currentTarget = elements[i];
        pointerEvent.interactable = targets[i];
        targets[i].fire(pointerEvent);

        if (pointerEvent.immediatePropagationStopped
                    || (pointerEvent.propagationStopped && elements[i + 1] !== pointerEvent.currentTarget)){
          break;
        }
      }

      if (createNewDoubleTap){
        var doubleTap = {};

        extend(doubleTap, pointerEvent);

        doubleTap.dt = interval;
        doubleTap.type = 'doubletap';

        this.collectEventTargets(doubleTap, event, eventTarget, 'doubletap');

        this.prevTap = doubleTap;
      }
      else if (eventType === 'tap'){
        this.prevTap = pointerEvent;
      }
    },

    validateSelector: function(pointer, event, matches, matchElements){
      for (var i = 0, len = matches.length; i < len; i++){
        var match = matches[i],
          matchElement = matchElements[i],
          action = validateAction(match.getAction(pointer, event, this, matchElement), match);

        if (action && withinInteractionLimit(match, matchElement, action)){
          this.target = match;
          this.element = matchElement;

          return action;
        }
      }
    },

    setSnapping: function(pageCoords, status){
      var snap = this.target.options[this.prepared.name].snap,
        targets = [],
        target,
        page,
        i;

      status = status || this.snapStatus;

      if (status.useStatusXY){
        page = { x: status.x, y: status.y };
      }
      else {
        var origin = getOriginXY(this.target, this.element);

        page = extend({}, pageCoords);

        page.x -= origin.x;
        page.y -= origin.y;
      }

      status.realX = page.x;
      status.realY = page.y;

      page.x = page.x - this.inertiaStatus.resumeDx;
      page.y = page.y - this.inertiaStatus.resumeDy;

      var len = snap.targets ? snap.targets.length : 0;

      for (var relIndex = 0; relIndex < this.snapOffsets.length; relIndex++){
        var relative = {
          x: page.x - this.snapOffsets[relIndex].x,
          y: page.y - this.snapOffsets[relIndex].y
        };

        for (i = 0; i < len; i++){
          if (isFunction(snap.targets[i])){
            target = snap.targets[i](relative.x, relative.y, this);
          }
          else {
            target = snap.targets[i];
          }

          if (!target){ continue; }

          targets.push({
            x: isNumber(target.x) ? (target.x + this.snapOffsets[relIndex].x) : relative.x,
            y: isNumber(target.y) ? (target.y + this.snapOffsets[relIndex].y) : relative.y,

            range: isNumber(target.range) ? target.range : snap.range
          });
        }
      }

      var closest = {
        target: null,
        inRange: false,
        distance: 0,
        range: 0,
        dx: 0,
        dy: 0
      };

      for (i = 0, len = targets.length; i < len; i++){
        target = targets[i];

        var range = target.range,
          dx = target.x - page.x,
          dy = target.y - page.y,
          distance = hypot(dx, dy),
          inRange = distance <= range;

                // Infinite targets count as being out of range
                // compared to non infinite ones that are in range
        if (range === Infinity && closest.inRange && closest.range !== Infinity){
          inRange = false;
        }

        if (!closest.target || (inRange
                    // is the closest target in range?
                    ? (closest.inRange && range !== Infinity
                        // the pointer is relatively deeper in this target
                        ? distance / range < closest.distance / closest.range
                        // this target has Infinite range and the closest doesn't
                        : (range === Infinity && closest.range !== Infinity)
                            // OR this target is closer that the previous closest
                            || distance < closest.distance)
                    // The other is not in range and the pointer is closer to this target
                    : (!closest.inRange && distance < closest.distance))){
          if (range === Infinity){
            inRange = true;
          }

          closest.target = target;
          closest.distance = distance;
          closest.range = range;
          closest.inRange = inRange;
          closest.dx = dx;
          closest.dy = dy;

          status.range = range;
        }
      }

      var snapChanged;

      if (closest.target){
        snapChanged = (status.snappedX !== closest.target.x || status.snappedY !== closest.target.y);

        status.snappedX = closest.target.x;
        status.snappedY = closest.target.y;
      }
      else {
        snapChanged = true;

        status.snappedX = NaN;
        status.snappedY = NaN;
      }

      status.dx = closest.dx;
      status.dy = closest.dy;

      status.changed = (snapChanged || (closest.inRange && !status.locked));
      status.locked = closest.inRange;

      return status;
    },

    setRestriction: function(pageCoords, status){
      var target = this.target,
        restrict = target && target.options[this.prepared.name].restrict,
        restriction = restrict && restrict.restriction,
        page;

      if (!restriction){
        return status;
      }

      status = status || this.restrictStatus;

      page = status.useStatusXY
                    ? page = { x: status.x, y: status.y }
                    : page = extend({}, pageCoords);

      if (status.snap && status.snap.locked){
        page.x += status.snap.dx || 0;
        page.y += status.snap.dy || 0;
      }

      page.x -= this.inertiaStatus.resumeDx;
      page.y -= this.inertiaStatus.resumeDy;

      status.dx = 0;
      status.dy = 0;
      status.restricted = false;

      var rect, restrictedX, restrictedY;

      if (isString(restriction)){
        if (restriction === 'parent'){
          restriction = parentElement(this.element);
        }
        else if (restriction === 'self'){
          restriction = target.getRect(this.element);
        }
                else {
          restriction = closest(this.element, restriction);
        }

        if (!restriction){ return status; }
      }

      if (isFunction(restriction)){
        restriction = restriction(page.x, page.y, this.element);
      }

      if (isElement(restriction)){
        restriction = getElementRect(restriction);
      }

      rect = restriction;

      if (!restriction){
        restrictedX = page.x;
        restrictedY = page.y;
      }
            // object is assumed to have
            // x, y, width, height or
            // left, top, right, bottom
      else if ('x' in restriction && 'y' in restriction){
        restrictedX = Math.max(Math.min(rect.x + rect.width - this.restrictOffset.right, page.x), rect.x + this.restrictOffset.left);
        restrictedY = Math.max(Math.min(rect.y + rect.height - this.restrictOffset.bottom, page.y), rect.y + this.restrictOffset.top);
      }
            else {
        restrictedX = Math.max(Math.min(rect.right - this.restrictOffset.right, page.x), rect.left + this.restrictOffset.left);
        restrictedY = Math.max(Math.min(rect.bottom - this.restrictOffset.bottom, page.y), rect.top + this.restrictOffset.top);
      }

      status.dx = restrictedX - page.x;
      status.dy = restrictedY - page.y;

      status.changed = status.restrictedX !== restrictedX || status.restrictedY !== restrictedY;
      status.restricted = !!(status.dx || status.dy);

      status.restrictedX = restrictedX;
      status.restrictedY = restrictedY;

      return status;
    },

    checkAndPreventDefault: function(event, interactable, element){
      if (!(interactable = interactable || this.target)){ return; }

      var options = interactable.options,
        prevent = options.preventDefault;

      if (prevent === 'auto' && element && !/^(input|select|textarea)$/i.test(event.target.nodeName)){
                // do not preventDefault on pointerdown if the prepared action is a drag
                // and dragging can only start from a certain direction - this allows
                // a touch to pan the viewport if a drag isn't in the right direction
        if (/down|start/i.test(event.type)
                    && this.prepared.name === 'drag' && options.drag.axis !== 'xy'){
          return;
        }

                // with manualStart, only preventDefault while interacting
        if (options[this.prepared.name] && options[this.prepared.name].manualStart
                    && !this.interacting()){
          return;
        }

        event.preventDefault();
        return;
      }

      if (prevent === 'always'){
        event.preventDefault();
        return;
      }
    },

    calcInertia: function(status){
      var inertiaOptions = this.target.options[this.prepared.name].inertia,
        lambda = inertiaOptions.resistance,
        inertiaDur = -Math.log(inertiaOptions.endSpeed / status.v0) / lambda;

      status.x0 = this.prevEvent.pageX;
      status.y0 = this.prevEvent.pageY;
      status.t0 = status.startEvent.timeStamp / 1000;
      status.sx = status.sy = 0;

      status.modifiedXe = status.xe = (status.vx0 - inertiaDur) / lambda;
      status.modifiedYe = status.ye = (status.vy0 - inertiaDur) / lambda;
      status.te = inertiaDur;

      status.lambda_v0 = lambda / status.v0;
      status.one_ve_v0 = 1 - inertiaOptions.endSpeed / status.v0;
    },

    autoScrollMove: function(pointer){
      if (!(this.interacting()
                && checkAutoScroll(this.target, this.prepared.name))){
        return;
      }

      if (this.inertiaStatus.active){
        autoScroll.x = autoScroll.y = 0;
        return;
      }

      var top,
        right,
        bottom,
        left,
        options = this.target.options[this.prepared.name].autoScroll,
        container = options.container || getWindow(this.element);

      if (isWindow(container)){
        left = pointer.clientX < autoScroll.margin;
        top = pointer.clientY < autoScroll.margin;
        right = pointer.clientX > container.innerWidth - autoScroll.margin;
        bottom = pointer.clientY > container.innerHeight - autoScroll.margin;
      }
      else {
        var rect = getElementClientRect(container);

        left = pointer.clientX < rect.left + autoScroll.margin;
        top = pointer.clientY < rect.top + autoScroll.margin;
        right = pointer.clientX > rect.right - autoScroll.margin;
        bottom = pointer.clientY > rect.bottom - autoScroll.margin;
      }

      autoScroll.x = (right ? 1 : left ? -1 : 0);
      autoScroll.y = (bottom ? 1 : top ? -1 : 0);

      if (!autoScroll.isScrolling){
                // set the autoScroll properties to those of the target
        autoScroll.margin = options.margin;
        autoScroll.speed = options.speed;

        autoScroll.start(this);
      }
    },

    _updateEventTargets: function(target, currentTarget){
      this._eventTarget = target;
      this._curEventTarget = currentTarget;
    }

  };

  function getInteractionFromPointer(pointer, eventType, eventTarget){
    var i = 0, len = interactions.length,
      mouseEvent = (/mouse/i.test(pointer.pointerType || eventType)
                          // MSPointerEvent.MSPOINTER_TYPE_MOUSE
                          || pointer.pointerType === 4),
      interaction;

    var id = getPointerId(pointer);

        // try to resume inertia with a new pointer
    if (/down|start/i.test(eventType)){
      for (i = 0; i < len; i++){
        interaction = interactions[i];

        var element = eventTarget;

        if (interaction.inertiaStatus.active && interaction.target.options[interaction.prepared.name].inertia.allowResume
                    && (interaction.mouse === mouseEvent)){
          while (element){
                        // if the element is the interaction element
            if (element === interaction.element){
              return interaction;
            }
            element = parentElement(element);
          }
        }
      }
    }

        // if it's a mouse interaction
    if (mouseEvent || !supportsTouch || supportsPointerEvent){
            // find a mouse interaction that's not in inertia phase
      for (i = 0; i < len; i++){
        if (interactions[i].mouse && !interactions[i].inertiaStatus.active){
          return interactions[i];
        }
      }

            // find any interaction specifically for mouse.
            // if the eventType is a mousedown, and inertia is active
            // ignore the interaction
      for (i = 0; i < len; i++){
        if (interactions[i].mouse && !(/down/.test(eventType) && interactions[i].inertiaStatus.active)){
          return interaction;
        }
      }

            // create a new interaction for mouse
      interaction = new Interaction();
      interaction.mouse = true;

      return interaction;
    }

        // get interaction that has this pointer
    for (i = 0; i < len; i++){
      if (contains(interactions[i].pointerIds, id)){
        return interactions[i];
      }
    }

        // at this stage, a pointerUp should not return an interaction
    if (/up|end|out/i.test(eventType)){
      return null;
    }

        // get first idle interaction
    for (i = 0; i < len; i++){
      interaction = interactions[i];

      if ((!interaction.prepared.name || (interaction.target.options.gesture.enabled))
                && !interaction.interacting()
                && !(!mouseEvent && interaction.mouse)){
        return interaction;
      }
    }

    return new Interaction();
  }

  function doOnInteractions(method){
    return (function(event){
      var interaction,
        eventTarget = getActualElement(event.path
                                               ? event.path[0]
                                               : event.target),
        curEventTarget = getActualElement(event.currentTarget),
        i;

      if (supportsTouch && /touch/.test(event.type)){
        prevTouchTime = new Date().getTime();

        for (i = 0; i < event.changedTouches.length; i++){
          var pointer = event.changedTouches[i];

          interaction = getInteractionFromPointer(pointer, event.type, eventTarget);

          if (!interaction){ continue; }

          interaction._updateEventTargets(eventTarget, curEventTarget);

          interaction[method](pointer, event, eventTarget, curEventTarget);
        }
      }
      else {
        if (!supportsPointerEvent && /mouse/.test(event.type)){
                    // ignore mouse events while touch interactions are active
          for (i = 0; i < interactions.length; i++){
            if (!interactions[i].mouse && interactions[i].pointerIsDown){
              return;
            }
          }

                    // try to ignore mouse events that are simulated by the browser
                    // after a touch event
          if (new Date().getTime() - prevTouchTime < 500){
            return;
          }
        }

        interaction = getInteractionFromPointer(event, event.type, eventTarget);

        if (!interaction){ return; }

        interaction._updateEventTargets(eventTarget, curEventTarget);

        interaction[method](event, event, eventTarget, curEventTarget);
      }
    });
  }

  function InteractEvent(interaction, event, action, phase, element, related){
    var client,
      page,
      target = interaction.target,
      snapStatus = interaction.snapStatus,
      restrictStatus = interaction.restrictStatus,
      pointers = interaction.pointers,
      deltaSource = (target && target.options || defaultOptions).deltaSource,
      sourceX = deltaSource + 'X',
      sourceY = deltaSource + 'Y',
      options = target ? target.options : defaultOptions,
      origin = getOriginXY(target, element),
      starting = phase === 'start',
      ending = phase === 'end',
      coords = starting ? interaction.startCoords : interaction.curCoords;

    element = element || interaction.element;

    page = extend({}, coords.page);
    client = extend({}, coords.client);

    page.x -= origin.x;
    page.y -= origin.y;

    client.x -= origin.x;
    client.y -= origin.y;

    var relativePoints = options[action].snap && options[action].snap.relativePoints;

    if (checkSnap(target, action) && !(starting && relativePoints && relativePoints.length)){
      this.snap = {
        range: snapStatus.range,
        locked: snapStatus.locked,
        x: snapStatus.snappedX,
        y: snapStatus.snappedY,
        realX: snapStatus.realX,
        realY: snapStatus.realY,
        dx: snapStatus.dx,
        dy: snapStatus.dy
      };

      if (snapStatus.locked){
        page.x += snapStatus.dx;
        page.y += snapStatus.dy;
        client.x += snapStatus.dx;
        client.y += snapStatus.dy;
      }
    }

    if (checkRestrict(target, action) && !(starting && options[action].restrict.elementRect) && restrictStatus.restricted){
      page.x += restrictStatus.dx;
      page.y += restrictStatus.dy;
      client.x += restrictStatus.dx;
      client.y += restrictStatus.dy;

      this.restrict = {
        dx: restrictStatus.dx,
        dy: restrictStatus.dy
      };
    }

    this.pageX = page.x;
    this.pageY = page.y;
    this.clientX = client.x;
    this.clientY = client.y;

    this.x0 = interaction.startCoords.page.x - origin.x;
    this.y0 = interaction.startCoords.page.y - origin.y;
    this.clientX0 = interaction.startCoords.client.x - origin.x;
    this.clientY0 = interaction.startCoords.client.y - origin.y;
    this.ctrlKey = event.ctrlKey;
    this.altKey = event.altKey;
    this.shiftKey = event.shiftKey;
    this.metaKey = event.metaKey;
    this.button = event.button;
    this.buttons = event.buttons;
    this.target = element;
    this.t0 = interaction.downTimes[0];
    this.type = action + (phase || '');

    this.interaction = interaction;
    this.interactable = target;

    var inertiaStatus = interaction.inertiaStatus;

    if (inertiaStatus.active){
      this.detail = 'inertia';
    }

    if (related){
      this.relatedTarget = related;
    }

        // end event dx, dy is difference between start and end points
    if (ending){
      if (deltaSource === 'client'){
        this.dx = client.x - interaction.startCoords.client.x;
        this.dy = client.y - interaction.startCoords.client.y;
      }
      else {
        this.dx = page.x - interaction.startCoords.page.x;
        this.dy = page.y - interaction.startCoords.page.y;
      }
    }
    else if (starting){
      this.dx = 0;
      this.dy = 0;
    }
        // copy properties from previousmove if starting inertia
        else if (phase === 'inertiastart'){
          this.dx = interaction.prevEvent.dx;
          this.dy = interaction.prevEvent.dy;
        }
        else {
          if (deltaSource === 'client'){
            this.dx = client.x - interaction.prevEvent.clientX;
            this.dy = client.y - interaction.prevEvent.clientY;
          }
          else {
            this.dx = page.x - interaction.prevEvent.pageX;
            this.dy = page.y - interaction.prevEvent.pageY;
          }
        }
    if (interaction.prevEvent && interaction.prevEvent.detail === 'inertia'
            && !inertiaStatus.active
            && options[action].inertia && options[action].inertia.zeroResumeDelta){
      inertiaStatus.resumeDx += this.dx;
      inertiaStatus.resumeDy += this.dy;

      this.dx = this.dy = 0;
    }

    if (action === 'resize' && interaction.resizeAxes){
      if (options.resize.square){
        if (interaction.resizeAxes === 'y'){
          this.dx = this.dy;
        }
        else {
          this.dy = this.dx;
        }
        this.axes = 'xy';
      }
      else {
        this.axes = interaction.resizeAxes;

        if (interaction.resizeAxes === 'x'){
          this.dy = 0;
        }
        else if (interaction.resizeAxes === 'y'){
          this.dx = 0;
        }
      }
    }
    else if (action === 'gesture'){
      this.touches = [pointers[0], pointers[1]];

      if (starting){
        this.distance = touchDistance(pointers, deltaSource);
        this.box = touchBBox(pointers);
        this.scale = 1;
        this.ds = 0;
        this.angle = touchAngle(pointers, undefined, deltaSource);
        this.da = 0;
      }
      else if (ending || event instanceof InteractEvent){
        this.distance = interaction.prevEvent.distance;
        this.box = interaction.prevEvent.box;
        this.scale = interaction.prevEvent.scale;
        this.ds = this.scale - 1;
        this.angle = interaction.prevEvent.angle;
        this.da = this.angle - interaction.gesture.startAngle;
      }
            else {
        this.distance = touchDistance(pointers, deltaSource);
        this.box = touchBBox(pointers);
        this.scale = this.distance / interaction.gesture.startDistance;
        this.angle = touchAngle(pointers, interaction.gesture.prevAngle, deltaSource);

        this.ds = this.scale - interaction.gesture.prevScale;
        this.da = this.angle - interaction.gesture.prevAngle;
      }
    }

    if (starting){
      this.timeStamp = interaction.downTimes[0];
      this.dt = 0;
      this.duration = 0;
      this.speed = 0;
      this.velocityX = 0;
      this.velocityY = 0;
    }
    else if (phase === 'inertiastart'){
      this.timeStamp = interaction.prevEvent.timeStamp;
      this.dt = interaction.prevEvent.dt;
      this.duration = interaction.prevEvent.duration;
      this.speed = interaction.prevEvent.speed;
      this.velocityX = interaction.prevEvent.velocityX;
      this.velocityY = interaction.prevEvent.velocityY;
    }
        else {
      this.timeStamp = new Date().getTime();
      this.dt = this.timeStamp - interaction.prevEvent.timeStamp;
      this.duration = this.timeStamp - interaction.downTimes[0];

      if (event instanceof InteractEvent){
        var dx = this[sourceX] - interaction.prevEvent[sourceX],
          dy = this[sourceY] - interaction.prevEvent[sourceY],
          dt = this.dt / 1000;

        this.speed = hypot(dx, dy) / dt;
        this.velocityX = dx / dt;
        this.velocityY = dy / dt;
      }
            // if normal move or end event, use previous user event coords
      else {
                // speed and velocity in pixels per second
        this.speed = interaction.pointerDelta[deltaSource].speed;
        this.velocityX = interaction.pointerDelta[deltaSource].vx;
        this.velocityY = interaction.pointerDelta[deltaSource].vy;
      }
    }

    if ((ending || phase === 'inertiastart')
            && interaction.prevEvent.speed > 600 && this.timeStamp - interaction.prevEvent.timeStamp < 150){
      var angle = 180 * Math.atan2(interaction.prevEvent.velocityY, interaction.prevEvent.velocityX) / Math.PI,
        overlap = 22.5;

      if (angle < 0){
        angle += 360;
      }

      var left = 135 - overlap <= angle && angle < 225 + overlap,
        up = 225 - overlap <= angle && angle < 315 + overlap,

        right = !left && (315 - overlap <= angle || angle < 45 + overlap),
        down = !up && 45 - overlap <= angle && angle < 135 + overlap;

      this.swipe = {
        up: up,
        down: down,
        left: left,
        right: right,
        angle: angle,
        speed: interaction.prevEvent.speed,
        velocity: {
          x: interaction.prevEvent.velocityX,
          y: interaction.prevEvent.velocityY
        }
      };
    }
  }

  InteractEvent.prototype = {
    preventDefault: blank,
    stopImmediatePropagation: function(){
      this.immediatePropagationStopped = this.propagationStopped = true;
    },
    stopPropagation: function(){
      this.propagationStopped = true;
    }
  };

  function preventOriginalDefault(){
    this.originalEvent.preventDefault();
  }

  function getActionCursor(action){
    var cursor = '';

    if (action.name === 'drag'){
      cursor = actionCursors.drag;
    }
    if (action.name === 'resize'){
      if (action.axis){
        cursor = actionCursors[action.name + action.axis];
      }
      else if (action.edges){
        var cursorKey = 'resize',
          edgeNames = ['top', 'bottom', 'left', 'right'];

        for (var i = 0; i < 4; i++){
          if (action.edges[edgeNames[i]]){
            cursorKey += edgeNames[i];
          }
        }

        cursor = actionCursors[cursorKey];
      }
    }

    return cursor;
  }

  function checkResizeEdge(name, value, page, element, interactableElement, rect, margin){
        // false, '', undefined, null
    if (!value){ return false; }

        // true value, use pointer coords and element rect
    if (value === true){
            // if dimensions are negative, "switch" edges
      var width = isNumber(rect.width) ? rect.width : rect.right - rect.left,
        height = isNumber(rect.height) ? rect.height : rect.bottom - rect.top;

      if (width < 0){
        if (name === 'left'){ name = 'right'; }
        else if (name === 'right'){ name = 'left'; }
      }
      if (height < 0){
        if (name === 'top'){ name = 'bottom'; }
        else if (name === 'bottom'){ name = 'top'; }
      }

      if (name === 'left'){ return page.x < ((width >= 0 ? rect.left : rect.right) + margin); }
      if (name === 'top'){ return page.y < ((height >= 0 ? rect.top : rect.bottom) + margin); }

      if (name === 'right'){ return page.x > ((width >= 0 ? rect.right : rect.left) - margin); }
      if (name === 'bottom'){ return page.y > ((height >= 0 ? rect.bottom : rect.top) - margin); }
    }

        // the remaining checks require an element
    if (!isElement(element)){ return false; }

    return isElement(value)
                    // the value is an element to use as a resize handle
                    ? value === element
                    // otherwise check if element matches value as selector
                    : matchesUpTo(element, value, interactableElement);
  }

  function defaultActionChecker(pointer, interaction, element){
    var rect = this.getRect(element),
      shouldResize = false,
      action = null,
      resizeAxes = null,
      resizeEdges,
      page = extend({}, interaction.curCoords.page),
      options = this.options;

    if (!rect){ return null; }

    if (actionIsEnabled.resize && options.resize.enabled){
      var resizeOptions = options.resize;

      resizeEdges = {
        left: false, right: false, top: false, bottom: false
      };

            // if using resize.edges
      if (isObject(resizeOptions.edges)){
        for (var edge in resizeEdges){
          resizeEdges[edge] = checkResizeEdge(edge,
                                                        resizeOptions.edges[edge],
                                                        page,
                                                        interaction._eventTarget,
                                                        element,
                                                        rect,
                                                        resizeOptions.margin || margin);
        }

        resizeEdges.left = resizeEdges.left && !resizeEdges.right;
        resizeEdges.top = resizeEdges.top && !resizeEdges.bottom;

        shouldResize = resizeEdges.left || resizeEdges.right || resizeEdges.top || resizeEdges.bottom;
      }
      else {
        var right = options.resize.axis !== 'y' && page.x > (rect.right - margin),
          bottom = options.resize.axis !== 'x' && page.y > (rect.bottom - margin);

        shouldResize = right || bottom;
        resizeAxes = (right ? 'x' : '') + (bottom ? 'y' : '');
      }
    }

    action = shouldResize
            ? 'resize'
            : actionIsEnabled.drag && options.drag.enabled
                ? 'drag'
                : null;

    if (actionIsEnabled.gesture
            && interaction.pointerIds.length >= 2
            && !(interaction.dragging || interaction.resizing)){
      action = 'gesture';
    }

    if (action){
      return {
        name: action,
        axis: resizeAxes,
        edges: resizeEdges
      };
    }

    return null;
  }

    // Check if action is enabled globally and the current target supports it
    // If so, return the validated action. Otherwise, return null
  function validateAction(action, interactable){
    if (!isObject(action)){ return null; }

    var actionName = action.name,
      options = interactable.options;

    if (((actionName === 'resize' && options.resize.enabled)
            || (actionName === 'drag' && options.drag.enabled)
            || (actionName === 'gesture' && options.gesture.enabled))
            && actionIsEnabled[actionName]){
      if (actionName === 'resize' || actionName === 'resizeyx'){
        actionName = 'resizexy';
      }

      return action;
    }
    return null;
  }

  var listeners = {},
    interactionListeners = [
      'dragStart', 'dragMove', 'resizeStart', 'resizeMove', 'gestureStart', 'gestureMove',
      'pointerOver', 'pointerOut', 'pointerHover', 'selectorDown',
      'pointerDown', 'pointerMove', 'pointerUp', 'pointerCancel', 'pointerEnd',
      'addPointer', 'removePointer', 'recordPointer', 'autoScrollMove'
    ];

  for (var i = 0, len = interactionListeners.length; i < len; i++){
    var name = interactionListeners[i];

    listeners[name] = doOnInteractions(name);
  }

    // bound to the interactable context when a DOM event
    // listener is added to a selector interactable
  function delegateListener(event, useCapture){
    var fakeEvent = {},
      delegated = delegatedEvents[event.type],
      eventTarget = getActualElement(event.path
                                           ? event.path[0]
                                           : event.target),
      element = eventTarget;

    useCapture = useCapture ? true : false;

        // duplicate the event so that currentTarget can be changed
    for (var prop in event){
      fakeEvent[prop] = event[prop];
    }

    fakeEvent.originalEvent = event;
    fakeEvent.preventDefault = preventOriginalDefault;

        // climb up document tree looking for selector matches
    while (isElement(element)){
      for (var i = 0; i < delegated.selectors.length; i++){
        var selector = delegated.selectors[i],
          context = delegated.contexts[i];

        if (matchesSelector(element, selector)
                    && nodeContains(context, eventTarget)
                    && nodeContains(context, element)){
          var listeners = delegated.listeners[i];

          fakeEvent.currentTarget = element;

          for (var j = 0; j < listeners.length; j++){
            if (listeners[j][1] === useCapture){
              listeners[j][0](fakeEvent);
            }
          }
        }
      }

      element = parentElement(element);
    }
  }

  function delegateUseCapture(event){
    return delegateListener.call(this, event, true);
  }

  interactables.indexOfElement = function indexOfElement(element, context){
    context = context || document;

    for (var i = 0; i < this.length; i++){
      var interactable = this[i];

      if ((interactable.selector === element
                && (interactable._context === context))
                || (!interactable.selector && interactable._element === element)){
        return i;
      }
    }
    return -1;
  };

  interactables.get = function interactableGet(element, options){
    return this[this.indexOfElement(element, options && options.context)];
  };

  interactables.forEachSelector = function(callback){
    for (var i = 0; i < this.length; i++){
      var interactable = this[i];

      if (!interactable.selector){
        continue;
      }

      var ret = callback(interactable, interactable.selector, interactable._context, i, this);

      if (ret !== undefined){
        return ret;
      }
    }
  };

    /* \
     * interact
     [ method ]
     *
     * The methods of this variable can be used to set elements as
     * interactables and also to change various default settings.
     *
     * Calling it as a function and passing an element or a valid CSS selector
     * string returns an Interactable object which has various methods to
     * configure it.
     *
     - element (Element | string) The HTML or SVG Element to interact with or CSS selector
     = (object) An @Interactable
     *
     > Usage
     | interact(document.getElementById('draggable')).draggable(true);
     |
     | var rectables = interact('rect');
     | rectables
     |     .gesturable(true)
     |     .on('gesturemove', function (event) {
     |         // something cool...
     |     })
     |     .autoScroll(true);
    \*/
  function interact(element, options){
    return interactables.get(element, options) || new Interactable(element, options);
  }

    /* \
     * Interactable
     [ property ]
     **
     * Object type returned by @interact
    \*/
  function Interactable(element, options){
    this._element = element;
    this._iEvents = this._iEvents || {};

    var _window;

    if (trySelector(element)){
      this.selector = element;

      var context = options && options.context;

      _window = context ? getWindow(context) : window;

      if (context && (_window.Node
                    ? context instanceof _window.Node
                    : (isElement(context) || context === _window.document))){
        this._context = context;
      }
    }
    else {
      _window = getWindow(element);

      if (isElement(element, _window)){
        if (supportsPointerEvent){
          events.add(this._element, pEventTypes.down, listeners.pointerDown);
          events.add(this._element, pEventTypes.move, listeners.pointerHover);
        }
        else {
          events.add(this._element, 'mousedown', listeners.pointerDown);
          events.add(this._element, 'mousemove', listeners.pointerHover);
          events.add(this._element, 'touchstart', listeners.pointerDown);
          events.add(this._element, 'touchmove', listeners.pointerHover);
        }
      }
    }

    this._doc = _window.document;

    if (!contains(documents, this._doc)){
      listenToDocument(this._doc);
    }

    interactables.push(this);

    this.set(options);
  }

  Interactable.prototype = {
    setOnEvents: function(action, phases){
      if (action === 'drop'){
        if (isFunction(phases.ondrop)){ this.ondrop = phases.ondrop; }
        if (isFunction(phases.ondropactivate)){ this.ondropactivate = phases.ondropactivate; }
        if (isFunction(phases.ondropdeactivate)){ this.ondropdeactivate = phases.ondropdeactivate; }
        if (isFunction(phases.ondragenter)){ this.ondragenter = phases.ondragenter; }
        if (isFunction(phases.ondragleave)){ this.ondragleave = phases.ondragleave; }
        if (isFunction(phases.ondropmove)){ this.ondropmove = phases.ondropmove; }
      }
      else {
        action = 'on' + action;

        if (isFunction(phases.onstart)){ this[action + 'start' ] = phases.onstart; }
        if (isFunction(phases.onmove)){ this[action + 'move' ] = phases.onmove; }
        if (isFunction(phases.onend)){ this[action + 'end' ] = phases.onend; }
        if (isFunction(phases.oninertiastart)){ this[action + 'inertiastart' ] = phases.oninertiastart; }
      }

      return this;
    },

        /* \
         * Interactable.draggable
         [ method ]
         *
         * Gets or sets whether drag actions can be performed on the
         * Interactable
         *
         = (boolean) Indicates if this can be the target of drag events
         | var isDraggable = interact('ul li').draggable();
         * or
         - options (boolean | object) #optional true/false or An object with event listeners to be fired on drag events (object makes the Interactable draggable)
         = (object) This Interactable
         | interact(element).draggable({
         |     onstart: function (event) {},
         |     onmove : function (event) {},
         |     onend  : function (event) {},
         |
         |     // the axis in which the first movement must be
         |     // for the drag sequence to start
         |     // 'xy' by default - any direction
         |     axis: 'x' || 'y' || 'xy',
         |
         |     // max number of drags that can happen concurrently
         |     // with elements of this Interactable. Infinity by default
         |     max: Infinity,
         |
         |     // max number of drags that can target the same element+Interactable
         |     // 1 by default
         |     maxPerElement: 2
         | });
        \*/
    draggable: function(options){
      if (isObject(options)){
        this.options.drag.enabled = options.enabled === false ? false : true;
        this.setPerAction('drag', options);
        this.setOnEvents('drag', options);

        if (/^x$|^y$|^xy$/.test(options.axis)){
          this.options.drag.axis = options.axis;
        }
        else if (options.axis === null){
          delete this.options.drag.axis;
        }

        return this;
      }

      if (isBool(options)){
        this.options.drag.enabled = options;

        return this;
      }

      return this.options.drag;
    },

    setPerAction: function(action, options){
            // for all the default per-action options
      for (var option in options){
                // if this option exists for this action
        if (option in defaultOptions[action]){
                    // if the option in the options arg is an object value
          if (isObject(options[option])){
                        // duplicate the object
            this.options[action][option] = extend(this.options[action][option] || {}, options[option]);

            if (isObject(defaultOptions.perAction[option]) && 'enabled' in defaultOptions.perAction[option]){
              this.options[action][option].enabled = options[option].enabled === false ? false : true;
            }
          }
          else if (isBool(options[option]) && isObject(defaultOptions.perAction[option])){
            this.options[action][option].enabled = options[option];
          }
                    else if (options[option] !== undefined){
                        // or if it's not undefined, do a plain assignment
                      this.options[action][option] = options[option];
                    }
        }
      }
    },

        /* \
         * Interactable.dropzone
         [ method ]
         *
         * Returns or sets whether elements can be dropped onto this
         * Interactable to trigger drop events
         *
         * Dropzones can receive the following events:
         *  - `dropactivate` and `dropdeactivate` when an acceptable drag starts and ends
         *  - `dragenter` and `dragleave` when a draggable enters and leaves the dropzone
         *  - `dragmove` when a draggable that has entered the dropzone is moved
         *  - `drop` when a draggable is dropped into this dropzone
         *
         *  Use the `accept` option to allow only elements that match the given CSS selector or element.
         *
         *  Use the `overlap` option to set how drops are checked for. The allowed values are:
         *   - `'pointer'`, the pointer must be over the dropzone (default)
         *   - `'center'`, the draggable element's center must be over the dropzone
         *   - a number from 0-1 which is the `(intersection area) / (draggable area)`.
         *       e.g. `0.5` for drop to happen when half of the area of the
         *       draggable is over the dropzone
         *
         - options (boolean | object | null) #optional The new value to be set.
         | interact('.drop').dropzone({
         |   accept: '.can-drop' || document.getElementById('single-drop'),
         |   overlap: 'pointer' || 'center' || zeroToOne
         | }
         = (boolean | object) The current setting or this Interactable
        \*/
    dropzone: function(options){
      if (isObject(options)){
        this.options.drop.enabled = options.enabled === false ? false : true;
        this.setOnEvents('drop', options);

        if (/^(pointer|center)$/.test(options.overlap)){
          this.options.drop.overlap = options.overlap;
        }
        else if (isNumber(options.overlap)){
          this.options.drop.overlap = Math.max(Math.min(1, options.overlap), 0);
        }
        if ('accept' in options){
          this.options.drop.accept = options.accept;
        }
        if ('checker' in options){
          this.options.drop.checker = options.checker;
        }

        return this;
      }

      if (isBool(options)){
        this.options.drop.enabled = options;

        return this;
      }

      return this.options.drop;
    },

    dropCheck: function(dragEvent, event, draggable, draggableElement, dropElement, rect){
      var dropped = false;

            // if the dropzone has no rect (eg. display: none)
            // call the custom dropChecker or just return false
      if (!(rect = rect || this.getRect(dropElement))){
        return (this.options.drop.checker
                    ? this.options.drop.checker(dragEvent, event, dropped, this, dropElement, draggable, draggableElement)
                    : false);
      }

      var dropOverlap = this.options.drop.overlap;

      if (dropOverlap === 'pointer'){
        var page = getPageXY(dragEvent),
          origin = getOriginXY(draggable, draggableElement),
          horizontal,
          vertical;

        page.x += origin.x;
        page.y += origin.y;

        horizontal = (page.x > rect.left) && (page.x < rect.right);
        vertical = (page.y > rect.top) && (page.y < rect.bottom);

        dropped = horizontal && vertical;
      }

      var dragRect = draggable.getRect(draggableElement);

      if (dropOverlap === 'center'){
        var cx = dragRect.left + dragRect.width / 2,
          cy = dragRect.top + dragRect.height / 2;

        dropped = cx >= rect.left && cx <= rect.right && cy >= rect.top && cy <= rect.bottom;
      }

      if (isNumber(dropOverlap)){
        var overlapArea = (Math.max(0, Math.min(rect.right, dragRect.right) - Math.max(rect.left, dragRect.left))
                                  * Math.max(0, Math.min(rect.bottom, dragRect.bottom) - Math.max(rect.top, dragRect.top))),
          overlapRatio = overlapArea / (dragRect.width * dragRect.height);

        dropped = overlapRatio >= dropOverlap;
      }

      if (this.options.drop.checker){
        dropped = this.options.drop.checker(dragEvent, event, dropped, this, dropElement, draggable, draggableElement);
      }

      return dropped;
    },

        /* \
         * Interactable.dropChecker
         [ method ]
         *
         * DEPRECATED. Use interactable.dropzone({ checker: function... }) instead.
         *
         * Gets or sets the function used to check if a dragged element is
         * over this Interactable.
         *
         - checker (function) #optional The function that will be called when checking for a drop
         = (Function | Interactable) The checker function or this Interactable
         *
         * The checker function takes the following arguments:
         *
         - dragEvent (InteractEvent) The related dragmove or dragend event
         - event (TouchEvent | PointerEvent | MouseEvent) The user move/up/end Event related to the dragEvent
         - dropped (boolean) The value from the default drop checker
         - dropzone (Interactable) The dropzone interactable
         - dropElement (Element) The dropzone element
         - draggable (Interactable) The Interactable being dragged
         - draggableElement (Element) The actual element that's being dragged
         *
         > Usage:
         | interact(target)
         | .dropChecker(function(dragEvent,         // related dragmove or dragend event
         |                       event,             // TouchEvent/PointerEvent/MouseEvent
         |                       dropped,           // bool result of the default checker
         |                       dropzone,          // dropzone Interactable
         |                       dropElement,       // dropzone elemnt
         |                       draggable,         // draggable Interactable
         |                       draggableElement) {// draggable element
         |
         |   return dropped && event.target.hasAttribute('allow-drop');
         | }
        \*/
    dropChecker: function(checker){
      if (isFunction(checker)){
        this.options.drop.checker = checker;

        return this;
      }
      if (checker === null){
        delete this.options.getRect;

        return this;
      }

      return this.options.drop.checker;
    },

        /* \
         * Interactable.accept
         [ method ]
         *
         * Deprecated. add an `accept` property to the options object passed to
         * @Interactable.dropzone instead.
         *
         * Gets or sets the Element or CSS selector match that this
         * Interactable accepts if it is a dropzone.
         *
         - newValue (Element | string | null) #optional
         * If it is an Element, then only that element can be dropped into this dropzone.
         * If it is a string, the element being dragged must match it as a selector.
         * If it is null, the accept options is cleared - it accepts any element.
         *
         = (string | Element | null | Interactable) The current accept option if given `undefined` or this Interactable
        \*/
    accept: function(newValue){
      if (isElement(newValue)){
        this.options.drop.accept = newValue;

        return this;
      }

            // test if it is a valid CSS selector
      if (trySelector(newValue)){
        this.options.drop.accept = newValue;

        return this;
      }

      if (newValue === null){
        delete this.options.drop.accept;

        return this;
      }

      return this.options.drop.accept;
    },

        /* \
         * Interactable.resizable
         [ method ]
         *
         * Gets or sets whether resize actions can be performed on the
         * Interactable
         *
         = (boolean) Indicates if this can be the target of resize elements
         | var isResizeable = interact('input[type=text]').resizable();
         * or
         - options (boolean | object) #optional true/false or An object with event listeners to be fired on resize events (object makes the Interactable resizable)
         = (object) This Interactable
         | interact(element).resizable({
         |     onstart: function (event) {},
         |     onmove : function (event) {},
         |     onend  : function (event) {},
         |
         |     edges: {
         |       top   : true,       // Use pointer coords to check for resize.
         |       left  : false,      // Disable resizing from left edge.
         |       bottom: '.resize-s',// Resize if pointer target matches selector
         |       right : handleEl    // Resize if pointer target is the given Element
         |     },
         |
         |     // Width and height can be adjusted independently. When `true`, width and
         |     // height are adjusted at a 1:1 ratio.
         |     square: false,
         |
         |     // Width and height can be adjusted independently. When `true`, width and
         |     // height maintain the aspect ratio they had when resizing started.
         |     preserveAspectRatio: false,
         |
         |     // a value of 'none' will limit the resize rect to a minimum of 0x0
         |     // 'negate' will allow the rect to have negative width/height
         |     // 'reposition' will keep the width/height positive by swapping
         |     // the top and bottom edges and/or swapping the left and right edges
         |     invert: 'none' || 'negate' || 'reposition'
         |
         |     // limit multiple resizes.
         |     // See the explanation in the @Interactable.draggable example
         |     max: Infinity,
         |     maxPerElement: 1,
         | });
        \*/
    resizable: function(options){
      if (isObject(options)){
        this.options.resize.enabled = options.enabled === false ? false : true;
        this.setPerAction('resize', options);
        this.setOnEvents('resize', options);

        if (/^x$|^y$|^xy$/.test(options.axis)){
          this.options.resize.axis = options.axis;
        }
        else if (options.axis === null){
          this.options.resize.axis = defaultOptions.resize.axis;
        }

        if (isBool(options.preserveAspectRatio)){
          this.options.resize.preserveAspectRatio = options.preserveAspectRatio;
        }
        else if (isBool(options.square)){
          this.options.resize.square = options.square;
        }

        return this;
      }
      if (isBool(options)){
        this.options.resize.enabled = options;

        return this;
      }
      return this.options.resize;
    },

        /* \
         * Interactable.squareResize
         [ method ]
         *
         * Deprecated. Add a `square: true || false` property to @Interactable.resizable instead
         *
         * Gets or sets whether resizing is forced 1:1 aspect
         *
         = (boolean) Current setting
         *
         * or
         *
         - newValue (boolean) #optional
         = (object) this Interactable
        \*/
    squareResize: function(newValue){
      if (isBool(newValue)){
        this.options.resize.square = newValue;

        return this;
      }

      if (newValue === null){
        delete this.options.resize.square;

        return this;
      }

      return this.options.resize.square;
    },

        /* \
         * Interactable.gesturable
         [ method ]
         *
         * Gets or sets whether multitouch gestures can be performed on the
         * Interactable's element
         *
         = (boolean) Indicates if this can be the target of gesture events
         | var isGestureable = interact(element).gesturable();
         * or
         - options (boolean | object) #optional true/false or An object with event listeners to be fired on gesture events (makes the Interactable gesturable)
         = (object) this Interactable
         | interact(element).gesturable({
         |     onstart: function (event) {},
         |     onmove : function (event) {},
         |     onend  : function (event) {},
         |
         |     // limit multiple gestures.
         |     // See the explanation in @Interactable.draggable example
         |     max: Infinity,
         |     maxPerElement: 1,
         | });
        \*/
    gesturable: function(options){
      if (isObject(options)){
        this.options.gesture.enabled = options.enabled === false ? false : true;
        this.setPerAction('gesture', options);
        this.setOnEvents('gesture', options);

        return this;
      }

      if (isBool(options)){
        this.options.gesture.enabled = options;

        return this;
      }

      return this.options.gesture;
    },

        /* \
         * Interactable.autoScroll
         [ method ]
         **
         * Deprecated. Add an `autoscroll` property to the options object
         * passed to @Interactable.draggable or @Interactable.resizable instead.
         *
         * Returns or sets whether dragging and resizing near the edges of the
         * window/container trigger autoScroll for this Interactable
         *
         = (object) Object with autoScroll properties
         *
         * or
         *
         - options (object | boolean) #optional
         * options can be:
         * - an object with margin, distance and interval properties,
         * - true or false to enable or disable autoScroll or
         = (Interactable) this Interactable
        \*/
    autoScroll: function(options){
      if (isObject(options)){
        options = extend({ actions: ['drag', 'resize']}, options);
      }
      else if (isBool(options)){
        options = { actions: ['drag', 'resize'], enabled: options };
      }

      return this.setOptions('autoScroll', options);
    },

        /* \
         * Interactable.snap
         [ method ]
         **
         * Deprecated. Add a `snap` property to the options object passed
         * to @Interactable.draggable or @Interactable.resizable instead.
         *
         * Returns or sets if and how action coordinates are snapped. By
         * default, snapping is relative to the pointer coordinates. You can
         * change this by setting the
         * [`elementOrigin`](https://github.com/taye/interact.js/pull/72).
         **
         = (boolean | object) `false` if snap is disabled; object with snap properties if snap is enabled
         **
         * or
         **
         - options (object | boolean | null) #optional
         = (Interactable) this Interactable
         > Usage
         | interact(document.querySelector('#thing')).snap({
         |     targets: [
         |         // snap to this specific point
         |         {
         |             x: 100,
         |             y: 100,
         |             range: 25
         |         },
         |         // give this function the x and y page coords and snap to the object returned
         |         function (x, y) {
         |             return {
         |                 x: x,
         |                 y: (75 + 50 * Math.sin(x * 0.04)),
         |                 range: 40
         |             };
         |         },
         |         // create a function that snaps to a grid
         |         interact.createSnapGrid({
         |             x: 50,
         |             y: 50,
         |             range: 10,              // optional
         |             offset: { x: 5, y: 10 } // optional
         |         })
         |     ],
         |     // do not snap during normal movement.
         |     // Instead, trigger only one snapped move event
         |     // immediately before the end event.
         |     endOnly: true,
         |
         |     relativePoints: [
         |         { x: 0, y: 0 },  // snap relative to the top left of the element
         |         { x: 1, y: 1 },  // and also to the bottom right
         |     ],
         |
         |     // offset the snap target coordinates
         |     // can be an object with x/y or 'startCoords'
         |     offset: { x: 50, y: 50 }
         |   }
         | });
        \*/
    snap: function(options){
      var ret = this.setOptions('snap', options);

      if (ret === this){ return this; }

      return ret.drag;
    },

    setOptions: function(option, options){
      var actions = options && isArray(options.actions)
                    ? options.actions
                    : ['drag'];

      var i;

      if (isObject(options) || isBool(options)){
        for (i = 0; i < actions.length; i++){
          var action = /resize/.test(actions[i]) ? 'resize' : actions[i];

          if (!isObject(this.options[action])){ continue; }

          var thisOption = this.options[action][option];

          if (isObject(options)){
            extend(thisOption, options);
            thisOption.enabled = options.enabled === false ? false : true;

            if (option === 'snap'){
              if (thisOption.mode === 'grid'){
                thisOption.targets = [
                  interact.createSnapGrid(extend({
                    offset: thisOption.gridOffset || { x: 0, y: 0 }
                  }, thisOption.grid || {}))
                ];
              }
              else if (thisOption.mode === 'anchor'){
                thisOption.targets = thisOption.anchors;
              }
                            else if (thisOption.mode === 'path'){
                              thisOption.targets = thisOption.paths;
                            }

              if ('elementOrigin' in options){
                thisOption.relativePoints = [options.elementOrigin];
              }
            }
          }
          else if (isBool(options)){
            thisOption.enabled = options;
          }
        }

        return this;
      }

      var ret = {},
        allActions = ['drag', 'resize', 'gesture'];

      for (i = 0; i < allActions.length; i++){
        if (option in defaultOptions[allActions[i]]){
          ret[allActions[i]] = this.options[allActions[i]][option];
        }
      }

      return ret;
    },

        /* \
         * Interactable.inertia
         [ method ]
         **
         * Deprecated. Add an `inertia` property to the options object passed
         * to @Interactable.draggable or @Interactable.resizable instead.
         *
         * Returns or sets if and how events continue to run after the pointer is released
         **
         = (boolean | object) `false` if inertia is disabled; `object` with inertia properties if inertia is enabled
         **
         * or
         **
         - options (object | boolean | null) #optional
         = (Interactable) this Interactable
         > Usage
         | // enable and use default settings
         | interact(element).inertia(true);
         |
         | // enable and use custom settings
         | interact(element).inertia({
         |     // value greater than 0
         |     // high values slow the object down more quickly
         |     resistance     : 16,
         |
         |     // the minimum launch speed (pixels per second) that results in inertia start
         |     minSpeed       : 200,
         |
         |     // inertia will stop when the object slows down to this speed
         |     endSpeed       : 20,
         |
         |     // boolean; should actions be resumed when the pointer goes down during inertia
         |     allowResume    : true,
         |
         |     // boolean; should the jump when resuming from inertia be ignored in event.dx/dy
         |     zeroResumeDelta: false,
         |
         |     // if snap/restrict are set to be endOnly and inertia is enabled, releasing
         |     // the pointer without triggering inertia will animate from the release
         |     // point to the snaped/restricted point in the given amount of time (ms)
         |     smoothEndDuration: 300,
         |
         |     // an array of action types that can have inertia (no gesture)
         |     actions        : ['drag', 'resize']
         | });
         |
         | // reset custom settings and use all defaults
         | interact(element).inertia(null);
        \*/
    inertia: function(options){
      var ret = this.setOptions('inertia', options);

      if (ret === this){ return this; }

      return ret.drag;
    },

    getAction: function(pointer, event, interaction, element){
      var action = this.defaultActionChecker(pointer, interaction, element);

      if (this.options.actionChecker){
        return this.options.actionChecker(pointer, event, action, this, element, interaction);
      }

      return action;
    },

    defaultActionChecker: defaultActionChecker,

        /* \
         * Interactable.actionChecker
         [ method ]
         *
         * Gets or sets the function used to check action to be performed on
         * pointerDown
         *
         - checker (function | null) #optional A function which takes a pointer event, defaultAction string, interactable, element and interaction as parameters and returns an object with name property 'drag' 'resize' or 'gesture' and optionally an `edges` object with boolean 'top', 'left', 'bottom' and right props.
         = (Function | Interactable) The checker function or this Interactable
         *
         | interact('.resize-drag')
         |   .resizable(true)
         |   .draggable(true)
         |   .actionChecker(function (pointer, event, action, interactable, element, interaction) {
         |
         |   if (interact.matchesSelector(event.target, '.drag-handle') {
         |     // force drag with handle target
         |     action.name = drag;
         |   }
         |   else {
         |     // resize from the top and right edges
         |     action.name  = 'resize';
         |     action.edges = { top: true, right: true };
         |   }
         |
         |   return action;
         | });
        \*/
    actionChecker: function(checker){
      if (isFunction(checker)){
        this.options.actionChecker = checker;

        return this;
      }

      if (checker === null){
        delete this.options.actionChecker;

        return this;
      }

      return this.options.actionChecker;
    },

        /* \
         * Interactable.getRect
         [ method ]
         *
         * The default function to get an Interactables bounding rect. Can be
         * overridden using @Interactable.rectChecker.
         *
         - element (Element) #optional The element to measure.
         = (object) The object's bounding rectangle.
         o {
         o     top   : 0,
         o     left  : 0,
         o     bottom: 0,
         o     right : 0,
         o     width : 0,
         o     height: 0
         o }
        \*/
    getRect: function rectCheck(element){
      element = element || this._element;

      if (this.selector && !(isElement(element))){
        element = this._context.querySelector(this.selector);
      }

      return getElementRect(element);
    },

        /* \
         * Interactable.rectChecker
         [ method ]
         *
         * Returns or sets the function used to calculate the interactable's
         * element's rectangle
         *
         - checker (function) #optional A function which returns this Interactable's bounding rectangle. See @Interactable.getRect
         = (function | object) The checker function or this Interactable
        \*/
    rectChecker: function(checker){
      if (isFunction(checker)){
        this.getRect = checker;

        return this;
      }

      if (checker === null){
        delete this.options.getRect;

        return this;
      }

      return this.getRect;
    },

        /* \
         * Interactable.styleCursor
         [ method ]
         *
         * Returns or sets whether the action that would be performed when the
         * mouse on the element are checked on `mousemove` so that the cursor
         * may be styled appropriately
         *
         - newValue (boolean) #optional
         = (boolean | Interactable) The current setting or this Interactable
        \*/
    styleCursor: function(newValue){
      if (isBool(newValue)){
        this.options.styleCursor = newValue;

        return this;
      }

      if (newValue === null){
        delete this.options.styleCursor;

        return this;
      }

      return this.options.styleCursor;
    },

        /* \
         * Interactable.preventDefault
         [ method ]
         *
         * Returns or sets whether to prevent the browser's default behaviour
         * in response to pointer events. Can be set to:
         *  - `'always'` to always prevent
         *  - `'never'` to never prevent
         *  - `'auto'` to let interact.js try to determine what would be best
         *
         - newValue (string) #optional `true`, `false` or `'auto'`
         = (string | Interactable) The current setting or this Interactable
        \*/
    preventDefault: function(newValue){
      if (/^(always|never|auto)$/.test(newValue)){
        this.options.preventDefault = newValue;
        return this;
      }

      if (isBool(newValue)){
        this.options.preventDefault = newValue ? 'always' : 'never';
        return this;
      }

      return this.options.preventDefault;
    },

        /* \
         * Interactable.origin
         [ method ]
         *
         * Gets or sets the origin of the Interactable's element.  The x and y
         * of the origin will be subtracted from action event coordinates.
         *
         - origin (object | string) #optional An object eg. { x: 0, y: 0 } or string 'parent', 'self' or any CSS selector
         * OR
         - origin (Element) #optional An HTML or SVG Element whose rect will be used
         **
         = (object) The current origin or this Interactable
        \*/
    origin: function(newValue){
      if (trySelector(newValue)){
        this.options.origin = newValue;
        return this;
      }
      else if (isObject(newValue)){
        this.options.origin = newValue;
        return this;
      }

      return this.options.origin;
    },

        /* \
         * Interactable.deltaSource
         [ method ]
         *
         * Returns or sets the mouse coordinate types used to calculate the
         * movement of the pointer.
         *
         - newValue (string) #optional Use 'client' if you will be scrolling while interacting; Use 'page' if you want autoScroll to work
         = (string | object) The current deltaSource or this Interactable
        \*/
    deltaSource: function(newValue){
      if (newValue === 'page' || newValue === 'client'){
        this.options.deltaSource = newValue;

        return this;
      }

      return this.options.deltaSource;
    },

        /* \
         * Interactable.restrict
         [ method ]
         **
         * Deprecated. Add a `restrict` property to the options object passed to
         * @Interactable.draggable, @Interactable.resizable or @Interactable.gesturable instead.
         *
         * Returns or sets the rectangles within which actions on this
         * interactable (after snap calculations) are restricted. By default,
         * restricting is relative to the pointer coordinates. You can change
         * this by setting the
         * [`elementRect`](https://github.com/taye/interact.js/pull/72).
         **
         - options (object) #optional an object with keys drag, resize, and/or gesture whose values are rects, Elements, CSS selectors, or 'parent' or 'self'
         = (object) The current restrictions object or this Interactable
         **
         | interact(element).restrict({
         |     // the rect will be `interact.getElementRect(element.parentNode)`
         |     drag: element.parentNode,
         |
         |     // x and y are relative to the the interactable's origin
         |     resize: { x: 100, y: 100, width: 200, height: 200 }
         | })
         |
         | interact('.draggable').restrict({
         |     // the rect will be the selected element's parent
         |     drag: 'parent',
         |
         |     // do not restrict during normal movement.
         |     // Instead, trigger only one restricted move event
         |     // immediately before the end event.
         |     endOnly: true,
         |
         |     // https://github.com/taye/interact.js/pull/72#issue-41813493
         |     elementRect: { top: 0, left: 0, bottom: 1, right: 1 }
         | });
        \*/
    restrict: function(options){
      if (!isObject(options)){
        return this.setOptions('restrict', options);
      }

      var actions = ['drag', 'resize', 'gesture'],
        ret;

      for (var i = 0; i < actions.length; i++){
        var action = actions[i];

        if (action in options){
          var perAction = extend({
            actions: [action],
            restriction: options[action]
          }, options);

          ret = this.setOptions('restrict', perAction);
        }
      }

      return ret;
    },

        /* \
         * Interactable.context
         [ method ]
         *
         * Gets the selector context Node of the Interactable. The default is `window.document`.
         *
         = (Node) The context Node of this Interactable
         **
        \*/
    context: function(){
      return this._context;
    },

    _context: document,

        /* \
         * Interactable.ignoreFrom
         [ method ]
         *
         * If the target of the `mousedown`, `pointerdown` or `touchstart`
         * event or any of it's parents match the given CSS selector or
         * Element, no drag/resize/gesture is started.
         *
         - newValue (string | Element | null) #optional a CSS selector string, an Element or `null` to not ignore any elements
         = (string | Element | object) The current ignoreFrom value or this Interactable
         **
         | interact(element, { ignoreFrom: document.getElementById('no-action') });
         | // or
         | interact(element).ignoreFrom('input, textarea, a');
        \*/
    ignoreFrom: function(newValue){
      if (trySelector(newValue)){            // CSS selector to match event.target
        this.options.ignoreFrom = newValue;
        return this;
      }

      if (isElement(newValue)){              // specific element
        this.options.ignoreFrom = newValue;
        return this;
      }

      return this.options.ignoreFrom;
    },

        /* \
         * Interactable.allowFrom
         [ method ]
         *
         * A drag/resize/gesture is started only If the target of the
         * `mousedown`, `pointerdown` or `touchstart` event or any of it's
         * parents match the given CSS selector or Element.
         *
         - newValue (string | Element | null) #optional a CSS selector string, an Element or `null` to allow from any element
         = (string | Element | object) The current allowFrom value or this Interactable
         **
         | interact(element, { allowFrom: document.getElementById('drag-handle') });
         | // or
         | interact(element).allowFrom('.handle');
        \*/
    allowFrom: function(newValue){
      if (trySelector(newValue)){            // CSS selector to match event.target
        this.options.allowFrom = newValue;
        return this;
      }

      if (isElement(newValue)){              // specific element
        this.options.allowFrom = newValue;
        return this;
      }

      return this.options.allowFrom;
    },

        /* \
         * Interactable.element
         [ method ]
         *
         * If this is not a selector Interactable, it returns the element this
         * interactable represents
         *
         = (Element) HTML / SVG Element
        \*/
    element: function(){
      return this._element;
    },

        /* \
         * Interactable.fire
         [ method ]
         *
         * Calls listeners for the given InteractEvent type bound globally
         * and directly to this Interactable
         *
         - iEvent (InteractEvent) The InteractEvent object to be fired on this Interactable
         = (Interactable) this Interactable
        \*/
    fire: function(iEvent){
      if (!(iEvent && iEvent.type) || !contains(eventTypes, iEvent.type)){
        return this;
      }

      var listeners,
        i,
        len,
        onEvent = 'on' + iEvent.type,
        funcName = '';

            // Interactable#on() listeners
      if (iEvent.type in this._iEvents){
        listeners = this._iEvents[iEvent.type];

        for (i = 0, len = listeners.length; i < len && !iEvent.immediatePropagationStopped; i++){
          funcName = listeners[i].name;
          listeners[i](iEvent);
        }
      }

            // interactable.onevent listener
      if (isFunction(this[onEvent])){
        funcName = this[onEvent].name;
        this[onEvent](iEvent);
      }

            // interact.on() listeners
      if (iEvent.type in globalEvents && (listeners = globalEvents[iEvent.type])){
        for (i = 0, len = listeners.length; i < len && !iEvent.immediatePropagationStopped; i++){
          funcName = listeners[i].name;
          listeners[i](iEvent);
        }
      }

      return this;
    },

        /* \
         * Interactable.on
         [ method ]
         *
         * Binds a listener for an InteractEvent or DOM event.
         *
         - eventType  (string | array | object) The types of events to listen for
         - listener   (function) The function to be called on the given event(s)
         - useCapture (boolean) #optional useCapture flag for addEventListener
         = (object) This Interactable
        \*/
    on: function(eventType, listener, useCapture){
      var i;

      if (isString(eventType) && eventType.search(' ') !== -1){
        eventType = eventType.trim().split(/ +/);
      }

      if (isArray(eventType)){
        for (i = 0; i < eventType.length; i++){
          this.on(eventType[i], listener, useCapture);
        }

        return this;
      }

      if (isObject(eventType)){
        for (var prop in eventType){
          this.on(prop, eventType[prop], listener);
        }

        return this;
      }

      if (eventType === 'wheel'){
        eventType = wheelEvent;
      }

            // convert to boolean
      useCapture = useCapture ? true : false;

      if (contains(eventTypes, eventType)){
                // if this type of event was never bound to this Interactable
        if (!(eventType in this._iEvents)){
          this._iEvents[eventType] = [listener];
        }
        else {
          this._iEvents[eventType].push(listener);
        }
      }
            // delegated event for selector
      else if (this.selector){
        if (!delegatedEvents[eventType]){
          delegatedEvents[eventType] = {
            selectors: [],
            contexts: [],
            listeners: []
          };

                    // add delegate listener functions
          for (i = 0; i < documents.length; i++){
            events.add(documents[i], eventType, delegateListener);
            events.add(documents[i], eventType, delegateUseCapture, true);
          }
        }

        var delegated = delegatedEvents[eventType],
          index;

        for (index = delegated.selectors.length - 1; index >= 0; index--){
          if (delegated.selectors[index] === this.selector
                        && delegated.contexts[index] === this._context){
            break;
          }
        }

        if (index === -1){
          index = delegated.selectors.length;

          delegated.selectors.push(this.selector);
          delegated.contexts.push(this._context);
          delegated.listeners.push([]);
        }

                // keep listener and useCapture flag
        delegated.listeners[index].push([listener, useCapture]);
      }
            else {
        events.add(this._element, eventType, listener, useCapture);
      }

      return this;
    },

        /* \
         * Interactable.off
         [ method ]
         *
         * Removes an InteractEvent or DOM event listener
         *
         - eventType  (string | array | object) The types of events that were listened for
         - listener   (function) The listener function to be removed
         - useCapture (boolean) #optional useCapture flag for removeEventListener
         = (object) This Interactable
        \*/
    off: function(eventType, listener, useCapture){
      var i;

      if (isString(eventType) && eventType.search(' ') !== -1){
        eventType = eventType.trim().split(/ +/);
      }

      if (isArray(eventType)){
        for (i = 0; i < eventType.length; i++){
          this.off(eventType[i], listener, useCapture);
        }

        return this;
      }

      if (isObject(eventType)){
        for (var prop in eventType){
          this.off(prop, eventType[prop], listener);
        }

        return this;
      }

      var eventList,
        index = -1;

            // convert to boolean
      useCapture = useCapture ? true : false;

      if (eventType === 'wheel'){
        eventType = wheelEvent;
      }

            // if it is an action event type
      if (contains(eventTypes, eventType)){
        eventList = this._iEvents[eventType];

        if (eventList && (index = indexOf(eventList, listener)) !== -1){
          this._iEvents[eventType].splice(index, 1);
        }
      }
            // delegated event
      else if (this.selector){
        var delegated = delegatedEvents[eventType],
          matchFound = false;

        if (!delegated){ return this; }

                // count from last index of delegated to 0
        for (index = delegated.selectors.length - 1; index >= 0; index--){
                    // look for matching selector and context Node
          if (delegated.selectors[index] === this.selector
                        && delegated.contexts[index] === this._context){
            var listeners = delegated.listeners[index];

                        // each item of the listeners array is an array: [function, useCaptureFlag]
            for (i = listeners.length - 1; i >= 0; i--){
              var fn = listeners[i][0],
                useCap = listeners[i][1];

                            // check if the listener functions and useCapture flags match
              if (fn === listener && useCap === useCapture){
                                // remove the listener from the array of listeners
                listeners.splice(i, 1);

                                // if all listeners for this interactable have been removed
                                // remove the interactable from the delegated arrays
                if (!listeners.length){
                  delegated.selectors.splice(index, 1);
                  delegated.contexts.splice(index, 1);
                  delegated.listeners.splice(index, 1);

                                    // remove delegate function from context
                  events.remove(this._context, eventType, delegateListener);
                  events.remove(this._context, eventType, delegateUseCapture, true);

                                    // remove the arrays if they are empty
                  if (!delegated.selectors.length){
                    delegatedEvents[eventType] = null;
                  }
                }

                                // only remove one listener
                matchFound = true;
                break;
              }
            }

            if (matchFound){ break; }
          }
        }
      }
            // remove listener from this Interatable's element
            else {
        events.remove(this._element, eventType, listener, useCapture);
      }

      return this;
    },

        /* \
         * Interactable.set
         [ method ]
         *
         * Reset the options of this Interactable
         - options (object) The new settings to apply
         = (object) This Interactable
        \*/
    set: function(options){
      if (!isObject(options)){
        options = {};
      }

      this.options = extend({}, defaultOptions.base);

      var i,
        actions = ['drag', 'drop', 'resize', 'gesture'],
        methods = ['draggable', 'dropzone', 'resizable', 'gesturable'],
        perActions = extend(extend({}, defaultOptions.perAction), options[action] || {});

      for (i = 0; i < actions.length; i++){
        var action = actions[i];

        this.options[action] = extend({}, defaultOptions[action]);

        this.setPerAction(action, perActions);

        this[methods[i]](options[action]);
      }

      var settings = [
        'accept', 'actionChecker', 'allowFrom', 'deltaSource',
        'dropChecker', 'ignoreFrom', 'origin', 'preventDefault',
        'rectChecker', 'styleCursor'
      ];

      for (i = 0, len = settings.length; i < len; i++){
        var setting = settings[i];

        this.options[setting] = defaultOptions.base[setting];

        if (setting in options){
          this[setting](options[setting]);
        }
      }

      return this;
    },

        /* \
         * Interactable.unset
         [ method ]
         *
         * Remove this interactable from the list of interactables and remove
         * it's drag, drop, resize and gesture capabilities
         *
         = (object) @interact
        \*/
    unset: function(){
      events.remove(this._element, 'all');

      if (!isString(this.selector)){
        events.remove(this, 'all');
        if (this.options.styleCursor){
          this._element.style.cursor = '';
        }
      }
      else {
                // remove delegated events
        for (var type in delegatedEvents){
          var delegated = delegatedEvents[type];

          for (var i = 0; i < delegated.selectors.length; i++){
            if (delegated.selectors[i] === this.selector
                            && delegated.contexts[i] === this._context){
              delegated.selectors.splice(i, 1);
              delegated.contexts.splice(i, 1);
              delegated.listeners.splice(i, 1);

                            // remove the arrays if they are empty
              if (!delegated.selectors.length){
                delegatedEvents[type] = null;
              }
            }

            events.remove(this._context, type, delegateListener);
            events.remove(this._context, type, delegateUseCapture, true);

            break;
          }
        }
      }

      this.dropzone(false);

      interactables.splice(indexOf(interactables, this), 1);

      return interact;
    }
  };

  function warnOnce(method, message){
    var warned = false;

    return function(){
      if (!warned){
        window.console.warn(message);
        warned = true;
      }

      return method.apply(this, arguments);
    };
  }

  Interactable.prototype.snap = warnOnce(Interactable.prototype.snap,
         'Interactable#snap is deprecated. See the new documentation for snapping at http://interactjs.io/docs/snapping');
  Interactable.prototype.restrict = warnOnce(Interactable.prototype.restrict,
         'Interactable#restrict is deprecated. See the new documentation for resticting at http://interactjs.io/docs/restriction');
  Interactable.prototype.inertia = warnOnce(Interactable.prototype.inertia,
         'Interactable#inertia is deprecated. See the new documentation for inertia at http://interactjs.io/docs/inertia');
  Interactable.prototype.autoScroll = warnOnce(Interactable.prototype.autoScroll,
         'Interactable#autoScroll is deprecated. See the new documentation for autoScroll at http://interactjs.io/docs/#autoscroll');
  Interactable.prototype.squareResize = warnOnce(Interactable.prototype.squareResize,
         'Interactable#squareResize is deprecated. See http://interactjs.io/docs/#resize-square');

  Interactable.prototype.accept = warnOnce(Interactable.prototype.accept,
         'Interactable#accept is deprecated. use Interactable#dropzone({ accept: target }) instead');
  Interactable.prototype.dropChecker = warnOnce(Interactable.prototype.dropChecker,
         'Interactable#dropChecker is deprecated. use Interactable#dropzone({ dropChecker: checkerFunction }) instead');
  Interactable.prototype.context = warnOnce(Interactable.prototype.context,
         'Interactable#context as a method is deprecated. It will soon be a DOM Node instead');

    /* \
     * interact.isSet
     [ method ]
     *
     * Check if an element has been set
     - element (Element) The Element being searched for
     = (boolean) Indicates if the element or CSS selector was previously passed to interact
    \*/
  interact.isSet = function(element, options){
    return interactables.indexOfElement(element, options && options.context) !== -1;
  };

    /* \
     * interact.on
     [ method ]
     *
     * Adds a global listener for an InteractEvent or adds a DOM event to
     * `document`
     *
     - type       (string | array | object) The types of events to listen for
     - listener   (function) The function to be called on the given event(s)
     - useCapture (boolean) #optional useCapture flag for addEventListener
     = (object) interact
    \*/
  interact.on = function(type, listener, useCapture){
    if (isString(type) && type.search(' ') !== -1){
      type = type.trim().split(/ +/);
    }

    if (isArray(type)){
      for (var i = 0; i < type.length; i++){
        interact.on(type[i], listener, useCapture);
      }

      return interact;
    }

    if (isObject(type)){
      for (var prop in type){
        interact.on(prop, type[prop], listener);
      }

      return interact;
    }

        // if it is an InteractEvent type, add listener to globalEvents
    if (contains(eventTypes, type)){
            // if this type of event was never bound
      if (!globalEvents[type]){
        globalEvents[type] = [listener];
      }
      else {
        globalEvents[type].push(listener);
      }
    }
        // If non InteractEvent type, addEventListener to document
    else {
      events.add(document, type, listener, useCapture);
    }

    return interact;
  };

    /* \
     * interact.off
     [ method ]
     *
     * Removes a global InteractEvent listener or DOM event from `document`
     *
     - type       (string | array | object) The types of events that were listened for
     - listener   (function) The listener function to be removed
     - useCapture (boolean) #optional useCapture flag for removeEventListener
     = (object) interact
     \*/
  interact.off = function(type, listener, useCapture){
    if (isString(type) && type.search(' ') !== -1){
      type = type.trim().split(/ +/);
    }

    if (isArray(type)){
      for (var i = 0; i < type.length; i++){
        interact.off(type[i], listener, useCapture);
      }

      return interact;
    }

    if (isObject(type)){
      for (var prop in type){
        interact.off(prop, type[prop], listener);
      }

      return interact;
    }

    if (!contains(eventTypes, type)){
      events.remove(document, type, listener, useCapture);
    }
    else {
      var index;

      if (type in globalEvents
                && (index = indexOf(globalEvents[type], listener)) !== -1){
        globalEvents[type].splice(index, 1);
      }
    }

    return interact;
  };

    /* \
     * interact.enableDragging
     [ method ]
     *
     * Deprecated.
     *
     * Returns or sets whether dragging is enabled for any Interactables
     *
     - newValue (boolean) #optional `true` to allow the action; `false` to disable action for all Interactables
     = (boolean | object) The current setting or interact
    \*/
  interact.enableDragging = warnOnce(function(newValue){
    if (newValue !== null && newValue !== undefined){
      actionIsEnabled.drag = newValue;

      return interact;
    }
    return actionIsEnabled.drag;
  }, 'interact.enableDragging is deprecated and will soon be removed.');

    /* \
     * interact.enableResizing
     [ method ]
     *
     * Deprecated.
     *
     * Returns or sets whether resizing is enabled for any Interactables
     *
     - newValue (boolean) #optional `true` to allow the action; `false` to disable action for all Interactables
     = (boolean | object) The current setting or interact
    \*/
  interact.enableResizing = warnOnce(function(newValue){
    if (newValue !== null && newValue !== undefined){
      actionIsEnabled.resize = newValue;

      return interact;
    }
    return actionIsEnabled.resize;
  }, 'interact.enableResizing is deprecated and will soon be removed.');

    /* \
     * interact.enableGesturing
     [ method ]
     *
     * Deprecated.
     *
     * Returns or sets whether gesturing is enabled for any Interactables
     *
     - newValue (boolean) #optional `true` to allow the action; `false` to disable action for all Interactables
     = (boolean | object) The current setting or interact
    \*/
  interact.enableGesturing = warnOnce(function(newValue){
    if (newValue !== null && newValue !== undefined){
      actionIsEnabled.gesture = newValue;

      return interact;
    }
    return actionIsEnabled.gesture;
  }, 'interact.enableGesturing is deprecated and will soon be removed.');

  interact.eventTypes = eventTypes;

    /* \
     * interact.debug
     [ method ]
     *
     * Returns debugging data
     = (object) An object with properties that outline the current state and expose internal functions and variables
    \*/
  interact.debug = function(){
    var interaction = interactions[0] || new Interaction();

    return {
      interactions: interactions,
      target: interaction.target,
      dragging: interaction.dragging,
      resizing: interaction.resizing,
      gesturing: interaction.gesturing,
      prepared: interaction.prepared,
      matches: interaction.matches,
      matchElements: interaction.matchElements,

      prevCoords: interaction.prevCoords,
      startCoords: interaction.startCoords,

      pointerIds: interaction.pointerIds,
      pointers: interaction.pointers,
      addPointer: listeners.addPointer,
      removePointer: listeners.removePointer,
      recordPointer: listeners.recordPointer,

      snap: interaction.snapStatus,
      restrict: interaction.restrictStatus,
      inertia: interaction.inertiaStatus,

      downTime: interaction.downTimes[0],
      downEvent: interaction.downEvent,
      downPointer: interaction.downPointer,
      prevEvent: interaction.prevEvent,

      Interactable: Interactable,
      interactables: interactables,
      pointerIsDown: interaction.pointerIsDown,
      defaultOptions: defaultOptions,
      defaultActionChecker: defaultActionChecker,

      actionCursors: actionCursors,
      dragMove: listeners.dragMove,
      resizeMove: listeners.resizeMove,
      gestureMove: listeners.gestureMove,
      pointerUp: listeners.pointerUp,
      pointerDown: listeners.pointerDown,
      pointerMove: listeners.pointerMove,
      pointerHover: listeners.pointerHover,

      eventTypes: eventTypes,

      events: events,
      globalEvents: globalEvents,
      delegatedEvents: delegatedEvents,

      prefixedPropREs: prefixedPropREs
    };
  };

    // expose the functions used to calculate multi-touch properties
  interact.getPointerAverage = pointerAverage;
  interact.getTouchBBox = touchBBox;
  interact.getTouchDistance = touchDistance;
  interact.getTouchAngle = touchAngle;

  interact.getElementRect = getElementRect;
  interact.getElementClientRect = getElementClientRect;
  interact.matchesSelector = matchesSelector;
  interact.closest = closest;

    /* \
     * interact.margin
     [ method ]
     *
     * Deprecated. Use `interact(target).resizable({ margin: number });` instead.
     * Returns or sets the margin for autocheck resizing used in
     * @Interactable.getAction. That is the distance from the bottom and right
     * edges of an element clicking in which will start resizing
     *
     - newValue (number) #optional
     = (number | interact) The current margin value or interact
    \*/
  interact.margin = warnOnce(function(newvalue){
    if (isNumber(newvalue)){
      margin = newvalue;

      return interact;
    }
    return margin;
  },
    'interact.margin is deprecated. Use interact(target).resizable({ margin: number }); instead.');

    /* \
     * interact.supportsTouch
     [ method ]
     *
     = (boolean) Whether or not the browser supports touch input
    \*/
  interact.supportsTouch = function(){
    return supportsTouch;
  };

    /* \
     * interact.supportsPointerEvent
     [ method ]
     *
     = (boolean) Whether or not the browser supports PointerEvents
    \*/
  interact.supportsPointerEvent = function(){
    return supportsPointerEvent;
  };

    /* \
     * interact.stop
     [ method ]
     *
     * Cancels all interactions (end events are not fired)
     *
     - event (Event) An event on which to call preventDefault()
     = (object) interact
    \*/
  interact.stop = function(event){
    for (var i = interactions.length - 1; i >= 0; i--){
      interactions[i].stop(event);
    }

    return interact;
  };

    /* \
     * interact.dynamicDrop
     [ method ]
     *
     * Returns or sets whether the dimensions of dropzone elements are
     * calculated on every dragmove or only on dragstart for the default
     * dropChecker
     *
     - newValue (boolean) #optional True to check on each move. False to check only before start
     = (boolean | interact) The current setting or interact
    \*/
  interact.dynamicDrop = function(newValue){
    if (isBool(newValue)){
            // if (dragging && dynamicDrop !== newValue && !newValue) {
                // calcRects(dropzones);
            // }

      dynamicDrop = newValue;

      return interact;
    }
    return dynamicDrop;
  };

    /* \
     * interact.pointerMoveTolerance
     [ method ]
     * Returns or sets the distance the pointer must be moved before an action
     * sequence occurs. This also affects tolerance for tap events.
     *
     - newValue (number) #optional The movement from the start position must be greater than this value
     = (number | Interactable) The current setting or interact
    \*/
  interact.pointerMoveTolerance = function(newValue){
    if (isNumber(newValue)){
      pointerMoveTolerance = newValue;

      return this;
    }

    return pointerMoveTolerance;
  };

    /* \
     * interact.maxInteractions
     [ method ]
     **
     * Returns or sets the maximum number of concurrent interactions allowed.
     * By default only 1 interaction is allowed at a time (for backwards
     * compatibility). To allow multiple interactions on the same Interactables
     * and elements, you need to enable it in the draggable, resizable and
     * gesturable `'max'` and `'maxPerElement'` options.
     **
     - newValue (number) #optional Any number. newValue <= 0 means no interactions.
    \*/
  interact.maxInteractions = function(newValue){
    if (isNumber(newValue)){
      maxInteractions = newValue;

      return this;
    }

    return maxInteractions;
  };

  interact.createSnapGrid = function(grid){
    return function(x, y){
      var offsetX = 0,
        offsetY = 0;

      if (isObject(grid.offset)){
        offsetX = grid.offset.x;
        offsetY = grid.offset.y;
      }

      var gridx = Math.round((x - offsetX) / grid.x),
        gridy = Math.round((y - offsetY) / grid.y),

        newX = gridx * grid.x + offsetX,
        newY = gridy * grid.y + offsetY;

      return {
        x: newX,
        y: newY,
        range: grid.range
      };
    };
  };

  function endAllInteractions(event){
    for (var i = 0; i < interactions.length; i++){
      interactions[i].pointerEnd(event, event);
    }
  }

  function listenToDocument(doc){
    if (contains(documents, doc)){ return; }

    var win = doc.defaultView || doc.parentWindow;

        // add delegate event listener
    for (var eventType in delegatedEvents){
      events.add(doc, eventType, delegateListener);
      events.add(doc, eventType, delegateUseCapture, true);
    }

    if (supportsPointerEvent){
      if (PointerEvent === win.MSPointerEvent){
        pEventTypes = {
          up: 'MSPointerUp', down: 'MSPointerDown', over: 'mouseover',
          out: 'mouseout', move: 'MSPointerMove', cancel: 'MSPointerCancel' };
      }
      else {
        pEventTypes = {
          up: 'pointerup', down: 'pointerdown', over: 'pointerover',
          out: 'pointerout', move: 'pointermove', cancel: 'pointercancel' };
      }

      events.add(doc, pEventTypes.down, listeners.selectorDown);
      events.add(doc, pEventTypes.move, listeners.pointerMove);
      events.add(doc, pEventTypes.over, listeners.pointerOver);
      events.add(doc, pEventTypes.out, listeners.pointerOut);
      events.add(doc, pEventTypes.up, listeners.pointerUp);
      events.add(doc, pEventTypes.cancel, listeners.pointerCancel);

            // autoscroll
      events.add(doc, pEventTypes.move, listeners.autoScrollMove);
    }
    else {
      events.add(doc, 'mousedown', listeners.selectorDown);
      events.add(doc, 'mousemove', listeners.pointerMove);
      events.add(doc, 'mouseup', listeners.pointerUp);
      events.add(doc, 'mouseover', listeners.pointerOver);
      events.add(doc, 'mouseout', listeners.pointerOut);

      events.add(doc, 'touchstart', listeners.selectorDown);
      events.add(doc, 'touchmove', listeners.pointerMove);
      events.add(doc, 'touchend', listeners.pointerUp);
      events.add(doc, 'touchcancel', listeners.pointerCancel);

            // autoscroll
      events.add(doc, 'mousemove', listeners.autoScrollMove);
      events.add(doc, 'touchmove', listeners.autoScrollMove);
    }

    events.add(win, 'blur', endAllInteractions);

    try {
      if (win.frameElement){
        var parentDoc = win.frameElement.ownerDocument,
          parentWindow = parentDoc.defaultView;

        events.add(parentDoc, 'mouseup', listeners.pointerEnd);
        events.add(parentDoc, 'touchend', listeners.pointerEnd);
        events.add(parentDoc, 'touchcancel', listeners.pointerEnd);
        events.add(parentDoc, 'pointerup', listeners.pointerEnd);
        events.add(parentDoc, 'MSPointerUp', listeners.pointerEnd);
        events.add(parentWindow, 'blur', endAllInteractions);
      }
    }
        catch (error){
          interact.windowParentError = error;
        }

        // prevent native HTML5 drag on interact.js target elements
    events.add(doc, 'dragstart', function(event){
      for (var i = 0; i < interactions.length; i++){
        var interaction = interactions[i];

        if (interaction.element
                    && (interaction.element === event.target
                        || nodeContains(interaction.element, event.target))){
          interaction.checkAndPreventDefault(event, interaction.target, interaction.element);
          return;
        }
      }
    });

    if (events.useAttachEvent){
            // For IE's lack of Event#preventDefault
      events.add(doc, 'selectstart', function(event){
        var interaction = interactions[0];

        if (interaction.currentAction()){
          interaction.checkAndPreventDefault(event);
        }
      });

            // For IE's bad dblclick event sequence
      events.add(doc, 'dblclick', doOnInteractions('ie8Dblclick'));
    }

    documents.push(doc);
  }

  listenToDocument(document);

  function indexOf(array, target){
    for (var i = 0, len = array.length; i < len; i++){
      if (array[i] === target){
        return i;
      }
    }

    return -1;
  }

  function contains(array, target){
    return indexOf(array, target) !== -1;
  }

  function matchesSelector(element, selector, nodeList){
    if (ie8MatchesSelector){
      return ie8MatchesSelector(element, selector, nodeList);
    }

        // remove /deep/ from selectors if shadowDOM polyfill is used
    if (window !== realWindow){
      selector = selector.replace(/\/deep\//g, ' ');
    }

    return element[prefixedMatchesSelector](selector);
  }

  function matchesUpTo(element, selector, limit){
    while (isElement(element)){
      if (matchesSelector(element, selector)){
        return true;
      }

      element = parentElement(element);

      if (element === limit){
        return matchesSelector(element, selector);
      }
    }

    return false;
  }

    // For IE8's lack of an Element#matchesSelector
    // taken from http://tanalin.com/en/blog/2012/12/matches-selector-ie8/ and modified
  if (!(prefixedMatchesSelector in Element.prototype) || !isFunction(Element.prototype[prefixedMatchesSelector])){
    ie8MatchesSelector = function(element, selector, elems){
      elems = elems || element.parentNode.querySelectorAll(selector);

      for (var i = 0, len = elems.length; i < len; i++){
        if (elems[i] === element){
          return true;
        }
      }

      return false;
    };
  }

    // requestAnimationFrame polyfill
  (function(){
    var lastTime = 0,
      vendors = ['ms', 'moz', 'webkit', 'o'];

    for (var x = 0; x < vendors.length && !realWindow.requestAnimationFrame; ++x){
      reqFrame = realWindow[vendors[x] + 'RequestAnimationFrame'];
      cancelFrame = realWindow[vendors[x] + 'CancelAnimationFrame'] || realWindow[vendors[x] + 'CancelRequestAnimationFrame'];
    }

    if (!reqFrame){
      reqFrame = function(callback){
        var currTime = new Date().getTime(),
          timeToCall = Math.max(0, 16 - (currTime - lastTime)),
          id = setTimeout(function(){ callback(currTime + timeToCall); },
                  timeToCall);
        lastTime = currTime + timeToCall;
        return id;
      };
    }

    if (!cancelFrame){
      cancelFrame = function(id){
        clearTimeout(id);
      };
    }
  }());

    /* global exports: true, module, define */

    // http://documentcloud.github.io/underscore/docs/underscore.html#section-11
  if (typeof exports !== 'undefined'){
    if (typeof module !== 'undefined' && module.exports){
      exports = module.exports = interact;
    }
    exports.interact = interact;
  }
    // AMD
  else if (typeof define === 'function' && define.amd){
    define('interact', function(){
      return interact;
    });
  }
    else {
    realWindow.interact = interact;
  }
}(typeof window === 'undefined' ? undefined : window));
